/* PilotNG Web-UI — Glassmorphism 2.0: SPA mit Live-Charts.
   Dashboard wird per Patch aktualisiert (kein Re-Render-Flackern);
   HP/Mana-Historie sammelt der Client aus dem 2s-Polling. */

const THEMES = {
  rosa:    { accent: '#e33a63', hover: '#f0557b', rgb: '227,58,99',  label: 'Rosa' },
  violett: { accent: '#8b5cf6', hover: '#a78bfa', rgb: '139,92,246', label: 'Violett' },
  cyan:    { accent: '#06b6d4', hover: '#22d3ee', rgb: '6,182,212',  label: 'Cyan' },
  gruen:   { accent: '#22c55e', hover: '#4ade80', rgb: '34,197,94',  label: 'Grün' },
  amber:   { accent: '#f59e0b', hover: '#fbbf24', rgb: '245,158,11', label: 'Amber' },
  blau:    { accent: '#3b82f6', hover: '#60a5fa', rgb: '59,130,246', label: 'Blau' },
};

const LABELS = {
  deathByPlayer: ['Von Spieler getötet — Bot gestoppt', 'bad'],
  deathByMonster: ['Von Monster getötet — Bergung läuft', 'warn'],
  deathRecoveryStarted: ['Bergung: laufe zum Todespunkt', 'info'],
  deathRecovered: ['Items geborgen — Hunt geht weiter', 'ok'],
  deathRecoverySkipped: ['Tod quittiert — weiter ohne Bergung', 'warn'],
  deathRecoveryFailed: ['Bergung gescheitert', 'bad'],
  deathAcknowledged: ['Tod quittiert (manuell weiter)', 'info'],
  deathRefillStarted: ['Notfall-Nachkauf: wenige Tränke holen', 'info'],
  deathRefillDone: ['Tränke nachgekauft — Bergung startet', 'ok'],
  deathRefillFailed: ['Trank-Nachkauf gescheitert', 'bad'],
  failsafe: ['Spieler gesichtet — ausgeloggt', 'bad'],
  loggedOut: ['Ausgeloggt', 'warn'], logoutFailed: ['Logout gescheitert', 'bad'],
  reloginAttempt: ['Relogin läuft', 'info'], reloginSuccess: ['Wieder eingeloggt', 'ok'],
  reloginFailed: ['Relogin gescheitert', 'bad'], smartResume: ['Route fortgesetzt', 'ok'],
  suppliesRefill: ['Vorräte niedrig — Refill gestartet', 'warn'],
  suppliesEmergency: ['Vorräte leer — Notfall-Stopp', 'bad'],
  suppliesLabelMissing: ['Refill-Label fehlt im Script', 'warn'],
  teleportDetected: ['Ungewollter Teleport — Stopp', 'bad'],
  escapeThroughFields: ['Durch Felder entkommen', 'info'],
  sessionLogout: ['Session: ausgeloggt', 'warn'],
  sessionOffline: ['Session: Offline-Pause', 'info'],
  sessionResume: ['Session: weiter geht’s', 'ok'],
  sessionSafeTimeout: ['Session: Sicherheits-Stopp', 'warn'],
  webCommand: ['Befehl aus der Web-UI', 'info'],
  webCommandFailed: ['Web-Befehl fehlgeschlagen', 'bad'],
  arduinoUnavailable: ['HID-Verbindung verloren — pausiert', 'bad'],
};

/* Line-Icons (ein Stil, stroke=currentColor) */
const I = {
  grid: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7.5" height="7.5" rx="2"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="2"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="2"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="2"/></svg>',
  compass: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M15.5 8.5 13.7 13.7 8.5 15.5l1.8-5.2z"/></svg>',
  heart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20.5C7 16.5 3.5 13.3 3.5 9.6 3.5 7 5.5 5 8 5c1.6 0 3.1.8 4 2.1C12.9 5.8 14.4 5 16 5c2.5 0 4.5 2 4.5 4.6 0 3.7-3.5 6.9-8.5 10.9z"/></svg>',
  zap: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h6l-1 8 9-12h-6z"/></svg>',
  bag: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 8h14l-1.2 12.2a2 2 0 0 1-2 1.8H8.2a2 2 0 0 1-2-1.8z"/><path d="M8.5 8V6.5a3.5 3.5 0 0 1 7 0V8"/></svg>',
  skull: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.5c-4.7 0-8.5 3.6-8.5 8.1 0 2.6 1.3 4.9 3.3 6.4V20a1.5 1.5 0 0 0 1.5 1.5h7.4a1.5 1.5 0 0 0 1.5-1.5v-3c2-1.5 3.3-3.8 3.3-6.4 0-4.5-3.8-8.1-8.5-8.1z"/><circle cx="9" cy="11" r="1.4" fill="currentColor" stroke="none"/><circle cx="15" cy="11" r="1.4" fill="currentColor" stroke="none"/></svg>',
  gear: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1a2 2 0 1 1-2.9 2.9l-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1a2 2 0 1 1-2.9-2.9l.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9l-.1-.1a2 2 0 1 1 2.9-2.9l.1.1a1.7 1.7 0 0 0 1.9.3h.1a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5h.1a1.7 1.7 0 0 0 1.9-.3l.1-.1a2 2 0 1 1 2.9 2.9l-.1.1a1.7 1.7 0 0 0-.3 1.9v.1a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>',
  user: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"/><path d="M4.5 20.5c1.3-3.3 4.1-5 7.5-5s6.2 1.7 7.5 5"/></svg>',
  play: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 4.8v14.4a1 1 0 0 0 1.5.9l11.5-7.2a1 1 0 0 0 0-1.8L8.5 3.9A1 1 0 0 0 7 4.8z"/></svg>',
  pause: '<svg viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16" rx="1.2"/><rect x="14" y="4" width="4" height="16" rx="1.2"/></svg>',
  pulse: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h4l2.5-6.5L14 18l2.5-6H21"/></svg>',
  drop: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3s6.5 6.6 6.5 11a6.5 6.5 0 0 1-13 0C5.5 9.6 12 3 12 3z"/></svg>',
  clock: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/></svg>',
  pin: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-7-5.3-7-11a7 7 0 0 1 14 0c0 5.7-7 11-7 11z"/><circle cx="12" cy="10" r="2.6"/></svg>',
};

const PAGES = [
  ['dashboard', 'grid', 'Dashboard'],
  ['cavebot', 'compass', 'Cave Bot'],
  ['healing', 'heart', 'Healing'],
  ['kombos', 'zap', 'Kombos'],
  ['inventar', 'bag', 'Inventar'],
  ['tod', 'skull', 'Tod'],
  ['einstellungen', 'gear', 'Einstellungen'],
  ['konto', 'user', 'Konto'],
];

let currentPage = 'dashboard';
let lastState = null;
let config = null;
let startup = null;
const hist = { hp: [], mana: [], t: [] };
const HIST_MAX = 90; // 3 Minuten bei 2s-Takt

/* ---------------------------------------------------------------- API */
async function getJson(url) {
  const r = await fetch(url, { cache: 'no-store' });
  return r.json();
}
async function postJson(url, payload) {
  const r = await fetch(url, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  return r.json();
}
const cmd = (action, value) => postJson('/api/command', { action, value });
const cfg = (path, value) => postJson('/api/command', { action: 'setConfig', path, value });

const fmt = s => {
  s = Math.floor(s || 0); const h = Math.floor(s / 3600), m = Math.floor(s % 3600 / 60);
  return h ? `${h} h ${String(m).padStart(2, '0')} min` : `${m} min`;
};
const esc = text => String(text ?? '').replace(/[&<>"]/g, c =>
  ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));

/* ---------------------------------------------------------------- Design */
function applyDesign(design) {
  const theme = THEMES[design.theme] || THEMES.rosa;
  const root = document.documentElement.style;
  root.setProperty('--accent', theme.accent);
  root.setProperty('--accent-hover', theme.hover);
  root.setProperty('--accent-rgb', theme.rgb);
  const hell = design.mode === 'hell';
  document.body.className = hell ? 'mode-hell' : 'mode-dunkel';
  // Regler: 0 = fast deckend & scharf, 100 = stark durchsichtig & viel Blur.
  const g = Math.max(0, Math.min(100, design.glass ?? 100)) / 100;
  root.setProperty('--glass-blur', `${Math.round(2 + 26 * g)}px`);
  root.setProperty('--glass-alpha', (hell ? 0.9 - 0.55 * g : 0.3 - 0.245 * g).toFixed(3));
  root.setProperty('--edge-alpha', (hell ? 0.5 + 0.3 * g : 0.07 + 0.1 * g).toFixed(3));
  root.setProperty('--shadow-alpha', (0.22 + 0.3 * g).toFixed(2));
  root.setProperty('--sat', (1 + 0.7 * g).toFixed(2));
}

/* ---------------------------------------------------------------- Charts */
function smoothPath(pts) {
  if (pts.length < 2) return '';
  let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[Math.max(0, i - 1)], p1 = pts[i], p2 = pts[i + 1],
      p3 = pts[Math.min(pts.length - 1, i + 2)];
    const c1x = p1[0] + (p2[0] - p0[0]) / 6, c1y = p1[1] + (p2[1] - p0[1]) / 6;
    const c2x = p2[0] - (p3[0] - p1[0]) / 6, c2y = p2[1] - (p3[1] - p1[1]) / 6;
    d += `C${c1x.toFixed(1)},${c1y.toFixed(1)} ${c2x.toFixed(1)},${c2y.toFixed(1)} ${p2[0].toFixed(1)},${p2[1].toFixed(1)}`;
  }
  return d;
}

function toPoints(values, w, h, pad, min, max) {
  const n = values.length;
  if (!n) return [];
  const span = Math.max(1e-6, max - min);
  return values.map((v, i) => [
    pad + (n === 1 ? 0 : i * (w - 2 * pad) / (n - 1)),
    h - pad - ((v - min) / span) * (h - 2 * pad),
  ]);
}

/* Grosser Verlaufs-Chart (HP + Mana) mit Grid, Fill und Tooltip */
function bigChart(id, w = 760, h = 296) {
  const pad = 26;
  const grid = [0, 25, 50, 75, 100].map(v => {
    const y = h - pad - v / 100 * (h - 2 * pad);
    return `<line x1="${pad}" x2="${w - pad}" y1="${y}" y2="${y}"/>` +
      `<text class="chart-axis" x="${pad - 7}" y="${y + 3}" text-anchor="end">${v}</text>`;
  }).join('');
  return `
  <div class="chart-wrap" id="${id}">
    <svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" data-w="${w}" data-h="${h}">
      <defs>
        <linearGradient id="${id}-hp" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--hp)" stop-opacity=".34"/>
          <stop offset="1" stop-color="var(--hp)" stop-opacity="0"/>
        </linearGradient>
        <linearGradient id="${id}-mana" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stop-color="var(--mana)" stop-opacity=".3"/>
          <stop offset="1" stop-color="var(--mana)" stop-opacity="0"/>
        </linearGradient>
      </defs>
      <g class="chart-grid">${grid}</g>
      <path class="fill-hp" fill="url(#${id}-hp)"/>
      <path class="fill-mana" fill="url(#${id}-mana)"/>
      <path class="chart-line chart-glow line-mana" stroke="var(--mana)"/>
      <path class="chart-line chart-glow line-hp" stroke="var(--hp)"/>
      <circle class="dot-hp" r="4" fill="var(--hp)" opacity="0"/>
      <circle class="dot-mana" r="4" fill="var(--mana)" opacity="0"/>
      <rect class="hit" x="0" y="0" width="${w}" height="${h}" fill="transparent"/>
    </svg>
    <div class="chart-tip"></div>
  </div>`;
}

function updateBigChart(id) {
  const wrap = document.getElementById(id);
  if (!wrap) return;
  const svg = wrap.querySelector('svg');
  const w = +svg.dataset.w, h = +svg.dataset.h, pad = 26;
  const hp = hist.hp.length ? hist.hp : [0];
  const mana = hist.mana.length ? hist.mana : [0];
  const hpPts = toPoints(hp, w, h, pad, 0, 100);
  const manaPts = toPoints(mana, w, h, pad, 0, 100);
  const close = pts => pts.length >= 2
    ? `${smoothPath(pts)}L${pts[pts.length - 1][0].toFixed(1)},${h - pad}L${pts[0][0].toFixed(1)},${h - pad}Z` : '';
  svg.querySelector('.line-hp').setAttribute('d', smoothPath(hpPts));
  svg.querySelector('.line-mana').setAttribute('d', smoothPath(manaPts));
  svg.querySelector('.fill-hp').setAttribute('d', close(hpPts));
  svg.querySelector('.fill-mana').setAttribute('d', close(manaPts));
  if (!wrap.dataset.bound) {
    wrap.dataset.bound = '1';
    const tip = wrap.querySelector('.chart-tip');
    const dotHp = svg.querySelector('.dot-hp'), dotMana = svg.querySelector('.dot-mana');
    svg.addEventListener('mousemove', event => {
      const n = hist.hp.length;
      if (n < 2) return;
      const box = svg.getBoundingClientRect();
      const x = (event.clientX - box.left) / box.width * w;
      const i = Math.max(0, Math.min(n - 1,
        Math.round((x - pad) / ((w - 2 * pad) / (n - 1)))));
      const pts = toPoints(hist.hp, w, h, pad, 0, 100);
      const mts = toPoints(hist.mana, w, h, pad, 0, 100);
      dotHp.setAttribute('cx', pts[i][0]); dotHp.setAttribute('cy', pts[i][1]);
      dotMana.setAttribute('cx', mts[i][0]); dotMana.setAttribute('cy', mts[i][1]);
      dotHp.setAttribute('opacity', 1); dotMana.setAttribute('opacity', 1);
      tip.innerHTML = `<b>${hist.t[i] || ''}</b><br>` +
        `<span style="color:var(--hp)">●</span> HP ${hist.hp[i]} %<br>` +
        `<span style="color:var(--mana)">●</span> Mana ${hist.mana[i]} %`;
      tip.style.left = (pts[i][0] / w * 100) + '%';
      tip.style.top = (Math.min(pts[i][1], mts[i][1]) / h * 100) + '%';
      tip.style.opacity = 1;
    });
    svg.addEventListener('mouseleave', () => {
      tip.style.opacity = 0;
      dotHp.setAttribute('opacity', 0); dotMana.setAttribute('opacity', 0);
    });
  }
}

/* Sparkline fuer Stat-Karten */
function sparkline(id, color, w = 220, h = 54) {
  return `<div class="spark" id="${id}">
    <svg viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" data-w="${w}" data-h="${h}">
      <defs><linearGradient id="${id}-g" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0" stop-color="${color}" stop-opacity=".3"/>
        <stop offset="1" stop-color="${color}" stop-opacity="0"/>
      </linearGradient></defs>
      <path class="sfill" fill="url(#${id}-g)"/>
      <path class="sline chart-line" stroke="${color}" stroke-width="2"/>
    </svg></div>`;
}

function updateSparkline(id, values) {
  const wrap = document.getElementById(id);
  if (!wrap || !values.length) return;
  const svg = wrap.querySelector('svg');
  const w = +svg.dataset.w, h = +svg.dataset.h;
  const min = Math.min(...values), max = Math.max(...values);
  const pts = toPoints(values, w, h, 5, min === max ? min - 1 : min, min === max ? max + 1 : max);
  svg.querySelector('.sline').setAttribute('d', smoothPath(pts));
  if (pts.length >= 2) {
    svg.querySelector('.sfill').setAttribute('d',
      `${smoothPath(pts)}L${pts[pts.length - 1][0].toFixed(1)},${h}L${pts[0][0].toFixed(1)},${h}Z`);
  }
}

/* Ring-Gauge */
const GAUGE_C = 2 * Math.PI * 38;
function gauge(id, color) {
  return `<div class="gauge-box"><div class="gauge" id="${id}">
    <svg width="92" height="92" viewBox="0 0 92 92">
      <circle class="track" cx="46" cy="46" r="38"/>
      <circle class="arc" cx="46" cy="46" r="38" stroke="${color}"
        stroke-dasharray="${GAUGE_C.toFixed(1)}" stroke-dashoffset="${GAUGE_C.toFixed(1)}"/>
    </svg><div class="pct">–</div>
  </div><div class="lbl">${id.endsWith('hp') ? 'HP' : 'MANA'}</div></div>`;
}
function updateGauge(id, value) {
  const el = document.getElementById(id);
  if (!el) return;
  const pct = value == null ? 0 : Math.max(0, Math.min(100, value));
  el.querySelector('.arc').style.strokeDashoffset =
    (GAUGE_C * (1 - pct / 100)).toFixed(1);
  el.querySelector('.pct').textContent = value == null ? '–' : `${value}%`;
}

/* ---------------------------------------------------------------- Bausteine */
const infoTip = text => `<span class="info" data-tip="${esc(text)}">?</span>`;
const card = (span, title, body, extraHead = '') =>
  `<div class="card glass c${span}" style="--i:${cardIndex++}"><h3>${title}${extraHead}</h3>${body}</div>`;
let cardIndex = 0;

function toggleRow(key, label, on, tip = '') {
  return `<div class="toggle-row" data-toggle="${key}"><span>${label}</span>${tip ? infoTip(tip) : ''}` +
    `<span class="grow"></span><span class="tgl${on ? ' on' : ''}"></span></div>`;
}
function numRow(label, value, path, tip = '') {
  return `<div class="row"><label>${label} ${tip ? infoTip(tip) : ''}</label>` +
    `<input class="input mini" type="number" min="0" value="${esc(value)}" data-cfg="${esc(path)}"></div>`;
}
function textRow(label, value, path, tip = '') {
  return `<div class="row"><label>${label} ${tip ? infoTip(tip) : ''}</label>` +
    `<input class="input wide" type="text" value="${esc(value)}" data-cfg="${esc(path)}"></div>`;
}
function bindInputs(root) {
  root.querySelectorAll('input[data-cfg]').forEach(input => {
    input.addEventListener('change', () => {
      const raw = input.type === 'number' ? Number(input.value || 0) : input.value;
      cfg(input.dataset.cfg, raw);
    });
  });
}
function bindToggles(root, map) {
  root.querySelectorAll('.toggle-row[data-toggle]').forEach(row => {
    row.onclick = async () => {
      const tgl = row.querySelector('.tgl');
      const on = !tgl.classList.contains('on');
      tgl.classList.toggle('on', on);
      await map[row.dataset.toggle](on);
    };
  });
}
/* ------------------------------------------------- Hardware-Karte */
function paintHardware(hw) {
  const sel = document.getElementById('displaySel');
  if (!sel) return;
  const list = hw.displays || [];
  sel.innerHTML = list.length
    ? list.map(d => `<option value="${esc(d.device)}"${d.device === hw.selected ? ' selected' : ''}>${esc(d.label)}</option>`).join('')
    : '<option value="">Kein Bildschirm erkannt</option>';
  sel.disabled = !list.length;
  document.querySelectorAll('#inputSeg button').forEach(b =>
    b.classList.toggle('on', b.dataset.input === hw.inputMethod));
  const status = document.getElementById('hwStatus');
  if (status) {
    status.textContent = hw.error
      ? 'Bildschirme konnten nicht gelesen werden: ' + hw.error
      : (list.length
        ? `${list.length} Bildschirm${list.length === 1 ? '' : 'e'} erkannt.`
        : 'Windows meldet gerade keinen Bildschirm.');
  }
}

async function loadHardware() {
  let hw;
  try { hw = await getJson('/api/hardware'); } catch (e) { return; }
  paintHardware(hw);
  const sel = document.getElementById('displaySel');
  const status = document.getElementById('hwStatus');
  if (sel) sel.onchange = async () => {
    status.textContent = 'Wird gespeichert …';
    const res = await postJson('/api/hardware', { display: sel.value });
    if (res.ok) { paintHardware(res); status.textContent = 'Bot-Bildschirm gespeichert.'; }
    else status.textContent = 'Fehler: ' + (res.error || '?');
  };
  document.querySelectorAll('#inputSeg button').forEach(btn => {
    btn.onclick = async () => {
      const res = await postJson('/api/hardware', { inputMethod: btn.dataset.input });
      if (res.ok) { paintHardware(res); status.textContent = 'Eingabe-Methode gespeichert.'; }
      else status.textContent = 'Fehler: ' + (res.error || '?');
    };
  });
  const reload = document.getElementById('reloadHw');
  if (reload) reload.onclick = () => loadHardware();
  const check = document.getElementById('checkHid');
  if (check) check.onclick = async () => {
    status.textContent = 'Verbindung wird geprüft …';
    try {
      const res = await getJson('/api/hid');
      status.textContent = res.message || (res.ok ? 'OK' : 'Nicht verbunden.');
    } catch (e) { status.textContent = 'Prüfung fehlgeschlagen.'; }
  };
}

function bindCfgToggles(root) {
  root.querySelectorAll('[data-cfg-tgl]').forEach(tgl => {
    tgl.onclick = () => {
      const on = !tgl.classList.contains('on');
      tgl.classList.toggle('on', on);
      cfg(tgl.dataset.cfgTgl, on);
    };
  });
}

function setTopbar(title, crumb, rightHtml = '') {
  document.getElementById('topbar').innerHTML =
    `<div><h1>${title}</h1></div><div class="crumb">${crumb}</div>` +
    `<div class="right">${rightHtml}</div>`;
}
const statusPill = active =>
  `<div class="pill" id="statusPill"><span class="dot${active ? ' on' : ''}"></span>` +
  `<span id="statusPillTxt">${active ? 'BOT AKTIV' : 'PAUSIERT'}</span></div>`;

/* ---------------------------------------------------------------- Seiten */
const renderers = {
  dashboard(app) {
    const s = lastState || {};
    const active = s.pause === false;
    const stt = s.stats || {};
    setTopbar('Dashboard', 'Live-Übersicht deiner Session', statusPill(active));
    app.innerHTML = `<div id="startBar"></div><div class="grid">
      ${card(4, 'HP', `<div class="stat"><div class="top">
          <span class="ico" style="background:rgba(52,211,153,.15);color:var(--hp)">${I.heart}</span>
          <div><div class="lbl">Lebenspunkte</div><div class="num" id="hpNum">–</div></div>
          <span class="delta" id="hpDelta"></span></div>
        ${sparkline('hpSpark', 'var(--hp)')}</div>`)}
      ${card(4, 'MANA', `<div class="stat"><div class="top">
          <span class="ico" style="background:rgba(96,165,250,.15);color:var(--mana)">${I.drop}</span>
          <div><div class="lbl">Mana</div><div class="num" id="manaNum">–</div></div>
          <span class="delta" id="manaDelta"></span></div>
        ${sparkline('manaSpark', 'var(--mana)')}</div>`)}
      ${card(4, 'SESSION', `<div class="stat"><div class="top">
          <span class="ico">${I.clock}</span>
          <div><div class="lbl" id="sessLbl">gespielt heute</div><div class="num" id="sessNum">–</div></div></div>
        <div class="accent-line" id="sessStats"></div></div>`)}
      ${card(8, 'VERLAUF · LETZTE 3 MINUTEN',
        bigChart('mainChart'),
        `<span class="legend"><span><i style="background:var(--hp)"></i>HP</span>` +
        `<span><i style="background:var(--mana)"></i>Mana</span></span>`)}
      ${card(4, 'STEUERUNG', `
        <div class="state" id="stateTxt">–</div>
        <div class="sub" id="taskTxt"></div>
        <button class="btn" id="playBtn2"></button>
        <div style="height:8px"></div>
        ${toggleRow('cavebot', 'Cavebot', s.toggles?.cavebot)}
        ${toggleRow('runToCreatures', 'Run to Creatures', s.toggles?.runToCreatures)}
        ${toggleRow('healing', 'Healing', s.toggles?.healing)}
        ${toggleRow('combo', 'Kombos', s.toggles?.combo)}`)}
      ${card(8, 'EREIGNISSE', `<div class="events" id="eventList"></div>`)}
      ${card(4, 'POSITION', `
        <div class="row" style="padding-top:0"><span class="ico" style="width:34px;height:34px;border-radius:11px;display:grid;place-items:center;background:rgba(var(--accent-rgb),.15);color:var(--accent)">${I.pin}</span>
          <div><div class="lbl" style="font-size:12px;color:var(--muted)">Koordinate</div>
          <div class="val" id="coordTxt" style="font-weight:700;color:var(--text)">–</div></div></div>
        <div class="bar-label"><span>Route</span><span id="wpTxt">–</span></div>
        <div class="bar"><i id="wpBar" style="background:linear-gradient(90deg,var(--accent),var(--accent-hover));box-shadow:0 0 12px rgba(var(--accent-rgb),.5);width:0"></i></div>
        <div class="sub" id="breakTxt"></div>`)}
    </div>`;
    document.getElementById('playBtn2').onclick = () =>
      cmd(lastState?.pause === false ? 'pause' : 'play').then(refresh);
    bindToggles(app, {
      cavebot: v => cmd('toggleCavebot', v),
      runToCreatures: v => cmd('toggleRunToCreatures', v),
      healing: v => cmd('toggleHealing', v),
      combo: v => cmd('toggleCombo', v),
    });
    updateDashboard();
  },

  cavebot(app) {
    const s = lastState || {};
    const wps = config?.waypoints || [];
    setTopbar('Cave Bot', 'Route, Recorder und Jagd-Verhalten',
      `<div class="pill">${wps.length} Wegpunkte</div>`);
    app.innerHTML = `<div class="grid">
      ${card(4, 'STEUERUNG', `
        ${toggleRow('cavebot', 'Cavebot aktiviert', s.toggles?.cavebot)}
        ${toggleRow('runToCreatures', 'Run to Creatures', s.toggles?.runToCreatures,
          'Läuft aktiv zu erkannten Monstern statt zu warten.')}
        <div class="row"><label>Route aufzeichnen ${infoTip('Startet/stoppt den Recorder: der Bot schreibt deine Laufwege als Wegpunkte mit.')}</label>
          <button class="btn small secondary" id="recBtn">Aufnahme an/aus</button></div>`)}
      ${card(8, 'WEGPUNKTE', `<div class="table-wrap"><table class="list">
        <tr><th>#</th><th>Label</th><th>Typ</th><th>Koordinate</th></tr>
        ${wps.map((w, i) => `<tr><td>${i}</td><td>${esc(w.label || '—')}</td>` +
          `<td><span class="tag info">${esc(w.type)}</span></td>` +
          `<td>${(w.coordinate || []).join(', ')}</td></tr>`).join('') ||
        '<tr><td colspan="4"><div class="empty">Keine Wegpunkte geladen.</div></td></tr>'}
        </table></div>`)}
    </div>`;
    bindToggles(app, {
      cavebot: v => cmd('toggleCavebot', v),
      runToCreatures: v => cmd('toggleRunToCreatures', v),
    });
    document.getElementById('recBtn').onclick = () => cmd('toggleRecording');
  },

  healing(app) {
    const h = config?.healing || {};
    const p = h.potions || {}, sp = h.spells || {}, food = h.eatFood || {};
    setTopbar('Healing', 'Tränke, Zauber und Essen');
    const potionRow = (name, key, field) => `
      <div class="row"><label>${name}</label>
        <input class="input mini" value="${esc(p[key]?.hotkey || '')}" data-cfg="healing.potions.${key}.hotkey" title="Hotkey">
        <input class="input mini" type="number" value="${p[key]?.slot ?? ''}" data-cfg="healing.potions.${key}.slot" title="Action-Bar-Slot">
        <input class="input mini" type="number" value="${p[key]?.[field] ?? 0}" data-cfg="healing.potions.${key}.${field}" title="Schwelle %">
        <span class="tgl${p[key]?.enabled ? ' on' : ''}" data-cfg-tgl="healing.potions.${key}.enabled"></span></div>`;
    const spellRow = (name, key) => `
      <div class="row"><label>${name}</label>
        <input class="input mini" value="${esc(sp[key]?.hotkey || '')}" data-cfg="healing.spells.${key}.hotkey" title="Hotkey">
        <input class="input mini" type="number" value="${sp[key]?.hpPercentageLessThanOrEqual ?? 0}" data-cfg="healing.spells.${key}.hpPercentageLessThanOrEqual" title="HP % Schwelle">
        <span class="tgl${sp[key]?.enabled ? ' on' : ''}" data-cfg-tgl="healing.spells.${key}.enabled"></span></div>`;
    app.innerHTML = `<div class="grid">
      ${card(6, 'TRÄNKE', `<div class="hint">Spalten: Hotkey · Slot · Schwelle % · an/aus</div>
        ${potionRow('Health Potion', 'firstHealthPotion', 'hpPercentageLessThanOrEqual')}
        ${potionRow('Mana Potion', 'firstManaPotion', 'manaPercentageLessThanOrEqual')}
        <div class="section-title">Essen</div>
        <div class="row"><label>eatFood</label>
          <input class="input mini" value="${esc(food.hotkey || '')}" data-cfg="healing.eatFood.hotkey">
          <span class="tgl${food.enabled ? ' on' : ''}" data-cfg-tgl="healing.eatFood.enabled"></span></div>`)}
      ${card(6, 'ZAUBER', `<div class="hint">Spalten: Hotkey · HP-Schwelle % · an/aus</div>
        ${spellRow('criticalHealing', 'criticalHealing')}
        ${spellRow('lightHealing', 'lightHealing')}
        ${spellRow('mediumHealing', 'mediumHealing')}
        ${spellRow('utura', 'utura')}
        ${spellRow('uturaGran', 'uturaGran')}`)}
    </div>`;
    bindInputs(app);
    bindCfgToggles(app);
  },

  kombos(app) {
    const combos = config?.combos || [];
    setTopbar('Kombos', 'Zauber-Kombinationen im Kampf',
      `<div class="pill">${combos.length} Kombos</div>`);
    app.innerHTML = `<div class="grid">
      ${card(12, 'KOMBO-LISTE', combos.length ? combos.map((c, i) => `
        <div class="toggle-row" data-combo="${i}"><span>${esc(c.name)}</span>
          <span class="tag info">${c.spells} Zauber</span>
          <span class="grow"></span><span class="tgl${c.enabled ? ' on' : ''}"></span></div>`).join('')
        : '<div class="empty">Keine Kombos angelegt.</div>')}
    </div>`;
    app.querySelectorAll('[data-combo]').forEach(row => {
      row.onclick = () => {
        const tgl = row.querySelector('.tgl');
        const on = !tgl.classList.contains('on');
        tgl.classList.toggle('on', on);
        cfg(`ng_comboSpells.items.${row.dataset.combo}.enabled`, on);
      };
    });
  },

  inventar(app) {
    const bp = config?.backpacks || {};
    const rules = config?.lootRules || [];
    setTopbar('Inventar', 'Backpacks und Loot-Regeln');
    app.innerHTML = `<div class="grid">
      ${card(5, 'BACKPACKS', `
        ${textRow('Main Backpack', bp.main || '', 'ng_backpacks.main', 'Rucksack, aus dem gespielt wird')}
        ${textRow('Loot Backpack', bp.loot || '', 'ng_backpacks.loot', 'Ziel für gesammelte Items')}
        ${toggleRow('drop', 'Rest wegwerfen', config?.lootDropUnwanted,
          'Erkannte Items ohne Regel werden weggeworfen — niemals Tränke, Coins oder Regel-Items.')}`)}
      ${card(7, 'LOOT-REGELN', rules.length ? `<div class="table-wrap"><table class="list">
        <tr><th>Item-ID</th><th>Aktion</th></tr>
        ${rules.map(r => `<tr><td>${r.itemId}</td><td>${r.drop
          ? '<span class="tag bad">wegwerfen</span>'
          : '<span class="tag ok">→ ' + esc(r.to || '?') + '</span>'}</td></tr>`).join('')}
        </table></div>` : '<div class="empty">Keine Regeln angelegt.</div>')}
    </div>`;
    bindInputs(app);
    bindToggles(app, { drop: v => cfg('ng_lootDropUnwanted', v) });
  },

  tod(app) {
    const d = config?.death || {};
    setTopbar('Tod', 'Sterben, Bergung und Notfall-Nachkauf');
    app.innerHTML = `<div class="grid">
      ${card(4, 'BERGUNG', `
        ${toggleRow('enabled', 'Automatische Bergung', d.enabled,
          'Der Bot läuft nach dem Tod selbst zu deiner Leiche, holt deine Sachen zurück und macht mit der Jagd weiter.')}
        ${toggleRow('pauseOnPlayerKill', 'Bei Spieler-Kill pausieren', d.pauseOnPlayerKill,
          'Wurdest du von einem Spieler getötet, stoppt der Bot und schreibt eine Nachricht ins Ereignis-Log.')}`)}
      ${card(4, 'NOTFALL-NACHKAUF', `
        ${numRow('Max. Tränke pro Sorte', d.refillCap, 'ng_deathConfig.refillCap', 'So viele Tränke kauft der Bot nach dem Tod höchstens — deine alten liegen ja noch in der Leiche.')}
        ${numRow('Mindest-Tränke vor Bergung', d.minPotions, 'ng_deathConfig.minPotions', 'Erst wenn du wieder so viele Tränke hast, läuft der Bot zur Leiche.')}
        ${numRow('Nachkauf-Abbruch (Min.)', d.refillTimeoutMinutes, 'ng_deathConfig.refillTimeoutMinutes', 'Klappt der Einkauf so lange nicht, bergt er trotzdem.')}`)}
      ${card(4, 'ERKENNUNG & ABBRUCH', `
        ${numRow('PK-Erkennung (Sek.)', d.killerWindowSeconds, 'ng_deathConfig.killerWindowSeconds', 'War in den letzten so vielen Sekunden ein fremder Spieler sichtbar, zählt der Tod als Spieler-Kill.')}
        ${numRow('Bergungs-Abbruch (Min.)', d.recoveryTimeoutMinutes, 'ng_deathConfig.recoveryTimeoutMinutes', 'Nach so vielen Minuten bricht der Bot die Bergung ab und macht normal weiter.')}`)}
    </div>`;
    bindInputs(app);
    bindToggles(app, {
      enabled: v => cfg('ng_deathConfig.enabled', v),
      pauseOnPlayerKill: v => cfg('ng_deathConfig.pauseOnPlayerKill', v),
    });
  },

  einstellungen(app) {
    const d = config?.design || { theme: 'rosa', mode: 'dunkel', glass: 100 };
    const hk = config?.hotkeys || {};
    const hur = config?.autoHur || {};
    const al = config?.alert || {};
    setTopbar('Einstellungen', 'Hardware, Design und Verhalten');
    app.innerHTML = `<div class="grid">
      ${card(12, 'HARDWARE', `
        <div class="hint">Auf welchem Bildschirm der Bot spielt und womit er tippt.</div>
        <div class="row"><label>Bot-Bildschirm ${infoTip('Auf diesem Bildschirm wird der Tibia-Client gestartet und aufgenommen. Er wird für den Bot auf 1920×1080 gestellt.')}</label>
          <select class="input wide" id="displaySel"><option>Wird geladen …</option></select>
          <button class="btn small secondary" id="reloadHw">Neu erkennen</button></div>
        <div class="row"><label>Eingabe-Methode ${infoTip('Womit der Bot Tasten und Maus bedient: per USB-Arduino (Kabel) oder über den Mac per Bluetooth.')}</label>
          <div class="seg" id="inputSeg">
            <button data-input="usb">Arduino (USB)</button>
            <button data-input="mac_bt">Mac (Bluetooth)</button>
          </div>
          <button class="btn small secondary" id="checkHid">Verbindung prüfen</button></div>
        <div class="sub" id="hwStatus"></div>`)}
      ${card(8, 'DESIGN', `
        <div class="section-title">Akzentfarbe</div>
        <div class="swatches">${Object.entries(THEMES).map(([name, t]) =>
          `<div class="swatch${name === d.theme ? ' selected' : ''}" data-theme="${name}"
            style="background:linear-gradient(135deg,${t.accent},${t.hover})" title="${t.label}"></div>`).join('')}</div>
        <div class="row"><label>Darstellung</label>
          <div class="seg" id="modeSeg">
            <button data-mode="dunkel" class="${d.mode !== 'hell' ? 'on' : ''}">Dunkel</button>
            <button data-mode="hell" class="${d.mode === 'hell' ? 'on' : ''}">Hell</button>
          </div></div>
        <div class="row"><label>Glas-Stärke ${infoTip('Wie durchsichtig und verschwommen die Panels sind: links fast deckend, rechts maximales Glas.')}</label>
          <input type="range" min="0" max="100" value="${d.glass}" id="glassSlider">
          <span class="val" id="glassVal" style="width:44px;text-align:right">${d.glass} %</span></div>`)}
      ${card(4, 'HOTKEYS', `
        ${textRow('Shovel', hk.shovel_hotkey || '', 'general_hotkeys.shovel_hotkey')}
        ${textRow('Rope', hk.rope_hotkey || '', 'general_hotkeys.rope_hotkey')}`)}
      ${card(4, 'AUTOHUR', `
        ${toggleRow('hur', 'Aktiviert', hur.enabled)}
        ${textRow('Spell', hur.spell || '', 'auto_hur.spell')}
        ${textRow('Hotkey', hur.hotkey || '', 'auto_hur.hotkey')}
        ${toggleRow('hurpz', 'Auch in PZ nutzen', hur.pz)}`)}
      ${card(4, 'ALARM', `
        ${toggleRow('alarm', 'Aktiviert', al.enabled)}
        ${toggleRow('alarmcave', 'Nur wenn Cave aktiv', al.cave)}
        ${toggleRow('alarmsay', '„?“ an Spieler senden', al.sayPlayer)}`)}
      ${card(4, 'VERBÜNDETE & FEINDE', `
        <div class="row"><label>Verbündete</label><span class="val">${(config?.allies || []).map(esc).join(', ') || '—'}</span></div>
        <div class="row"><label>Feinde</label><span class="val">${(config?.enemies || []).map(esc).join(', ') || '—'}</span></div>
        <div class="row"><input class="input wide" id="nameInput" placeholder="Spielername …"></div>
        <div class="row"><button class="btn small secondary" id="addAlly">+ Verbündeter</button>
          <button class="btn small secondary" id="addEnemy">+ Feind</button></div>`)}
    </div>`;
    app.querySelectorAll('.swatch').forEach(sw => {
      sw.onclick = async () => {
        await cfg('ui_theme', sw.dataset.theme);
        config.design.theme = sw.dataset.theme;
        applyDesign(config.design);
        renderPage();
      };
    });
    app.querySelectorAll('#modeSeg button').forEach(btn => {
      btn.onclick = async () => {
        await cfg('ui_mode', btn.dataset.mode);
        config.design.mode = btn.dataset.mode;
        applyDesign(config.design);
        renderPage();
      };
    });
    const slider = document.getElementById('glassSlider');
    const paintFill = () => slider.style.setProperty('--fill', slider.value + '%');
    paintFill();
    slider.oninput = () => {
      document.getElementById('glassVal').textContent = slider.value + ' %';
      config.design.glass = Number(slider.value);
      applyDesign(config.design);
      paintFill();
    };
    slider.onchange = () => cfg('ui_glass', Number(slider.value));
    bindInputs(app);
    bindToggles(app, {
      hur: v => cfg('auto_hur.enabled', v),
      hurpz: v => cfg('auto_hur.pz', v),
      alarm: v => cfg('alert.enabled', v),
      alarmcave: v => cfg('alert.cave', v),
      alarmsay: v => cfg('alert.sayPlayer', v),
    });
    const addName = async list => {
      const name = document.getElementById('nameInput').value.trim();
      if (!name) return;
      const names = [...(config[list] || [])];
      if (!names.includes(name)) names.push(name);
      await cfg(list, names);
      config[list] = names;
      renderPage();
    };
    document.getElementById('addAlly').onclick = () => addName('allies');
    document.getElementById('addEnemy').onclick = () => addName('enemies');
    loadHardware();
  },

  konto(app) {
    setTopbar('Konto', 'Tibia-Zugang, sicher verschlüsselt');
    app.innerHTML = `<div class="grid">
      ${card(6, 'TIBIA-ACCOUNT', `
        ${'' /* Passwort wird nie angezeigt; leer lassen behaelt das alte */}
        <div class="row"><label>Account</label><input class="input wide" id="accName" value=""></div>
        <div class="row"><label>Passwort</label><input class="input wide" id="accPass" type="password" placeholder="Leer = gespeichertes behalten"></div>
        <button class="btn" id="saveAcc">Sicher speichern</button>
        <div class="sub" id="accStatus">Wird mit Windows-DPAPI verschlüsselt gespeichert.</div>`)}
    </div>`;
    getJson('/api/account').then(a => {
      document.getElementById('accName').value = a.account || '';
    });
    document.getElementById('saveAcc').onclick = async () => {
      const res = await postJson('/api/account', {
        account: document.getElementById('accName').value,
        password: document.getElementById('accPass').value,
      });
      document.getElementById('accStatus').textContent =
        res.ok ? 'Gespeichert (Windows-DPAPI).' : 'Fehler: ' + (res.error || '?');
    };
  },
};

/* ------------------------------------------- Dashboard-Live-Patch */
const setText = (id, text) => {
  const el = document.getElementById(id);
  if (el && el.textContent !== String(text)) el.textContent = text;
};

function renderStartBar() {
  const bar = document.getElementById('startBar');
  if (!bar) return;
  // Wenn der Bot laeuft und alles ok ist, brauchen wir kein Banner.
  if (!startup || startup.state === 'idle' ||
      (startup.state === 'running' && lastState?.pause === false)) {
    if (bar.innerHTML) bar.innerHTML = '';
    return;
  }
  const kind = startup.state === 'error' ? 'error'
    : (startup.state === 'running' ? 'ok' : '');
  const html = `<div class="startbar glass ${kind}">
      <span class="ring"></span><span>${esc(startup.text)}</span>
      ${startup.state === 'error'
        ? '<button class="btn small" id="retryAll">Erneut versuchen</button>' : ''}
    </div>`;
  if (bar.dataset.html === html) return;
  bar.dataset.html = html;
  bar.innerHTML = html;
  const retry = document.getElementById('retryAll');
  if (retry) retry.onclick = () => cmd('startAll').then(refreshStartup);
}

async function refreshStartup() {
  try {
    startup = await getJson('/api/startup');
    renderStartBar();
  } catch (e) { /* Server kurz weg */ }
}

function updateDashboard() {
  const s = lastState;
  if (!s || currentPage !== 'dashboard') return;
  const active = s.pause === false;
  setText('hpNum', s.hpPercentage == null ? '–' : `${s.hpPercentage} %`);
  setText('manaNum', s.manaPercentage == null ? '–' : `${s.manaPercentage} %`);
  const delta = (id, arr) => {
    const el = document.getElementById(id);
    if (!el || arr.length < 2) return;
    const diff = arr[arr.length - 1] - arr[0];
    el.textContent = `${diff > 0 ? '+' : ''}${diff} %`;
    el.className = 'delta ' + (diff > 0 ? 'up' : diff < 0 ? 'down' : '');
  };
  delta('hpDelta', hist.hp);
  delta('manaDelta', hist.mana);
  setText('sessNum', fmt(s.playedSeconds));
  setText('sessLbl', s.breakRemainingSeconds > 0
    ? `Pause — noch ${fmt(s.breakRemainingSeconds)}` : 'gespielt heute');
  const stt = s.stats || {};
  setText('sessStats', `Loot/h ${stt.lootPerHour ?? 0} · Stuck ${stt.stucks ?? 0} · Sprünge ${stt.jumps ?? 0}`);
  const state = document.getElementById('stateTxt');
  if (state) {
    state.textContent = active ? 'AKTIV' : 'PAUSIERT';
    state.classList.toggle('ok', active);
  }
  setText('taskTxt', (s.task ? 'Aufgabe: ' + s.task : 'Keine aktive Aufgabe.') +
    (s.waypointIndex != null ? ` · Wegpunkt ${s.waypointIndex}` : ''));
  const playBtn2 = document.getElementById('playBtn2');
  if (playBtn2) playBtn2.innerHTML =
    (active ? I.pause : I.play) + (active ? 'Bot pausieren' : 'Bot starten');
  setText('coordTxt', s.coordinate ? s.coordinate.join(', ') : '–');
  const total = (config?.waypoints || []).length;
  const wp = s.waypointIndex;
  setText('wpTxt', wp != null && total ? `Wegpunkt ${wp} / ${total}` : '–');
  const bar = document.getElementById('wpBar');
  if (bar) bar.style.width = (wp != null && total ? (wp / total * 100) : 0) + '%';
  setText('breakTxt', s.breakRemainingSeconds > 0
    ? `Session-Pause: noch ${fmt(s.breakRemainingSeconds)}` : '');
  const list = document.getElementById('eventList');
  if (list) {
    const html = (s.events || []).map(e => {
      const [txt, kind] = LABELS[e.type] || [e.type, 'info'];
      return `<div class="event"><span class="t">${(e.at || '?').slice(11, 16)}</span>` +
        `<span class="tag ${kind}">${esc(txt)}</span></div>`;
    }).join('') || '<div class="empty">Noch keine Ereignisse.</div>';
    if (list.dataset.html !== html) { list.dataset.html = html; list.innerHTML = html; }
  }
  renderStartBar();
  updateGauge('gaugeHp', s.hpPercentage);
  updateGauge('gaugeMana', s.manaPercentage);
  updateSparkline('hpSpark', hist.hp);
  updateSparkline('manaSpark', hist.mana);
  updateBigChart('mainChart');
  const pillTxt = document.getElementById('statusPillTxt');
  if (pillTxt) {
    pillTxt.textContent = active ? 'BOT AKTIV' : 'PAUSIERT';
    document.querySelector('#statusPill .dot')?.classList.toggle('on', active);
  }
}

/* ---------------------------------------------------------------- Router */
function renderNav() {
  document.getElementById('nav').innerHTML = PAGES.map(([id, icon, label]) =>
    `<div class="nav-item${id === currentPage ? ' active' : ''}" data-page="${id}">${I[icon]}<span>${label}</span></div>`
  ).join('');
  document.querySelectorAll('.nav-item').forEach(item => {
    item.onclick = () => {
      currentPage = item.dataset.page;
      location.hash = currentPage;
      renderNav();
      renderPage(true);
    };
  });
}

function renderPage(animate = false) {
  cardIndex = 0;
  const app = document.getElementById('app');
  if (animate) {
    app.classList.remove('page');
    void app.offsetWidth; // Reflow → Einblend-Animation neu starten
    app.classList.add('page');
  }
  renderers[currentPage](app);
}

async function refresh() {
  try {
    lastState = await getJson('/api/state');
    if (lastState.hpPercentage != null || hist.hp.length) {
      hist.hp.push(lastState.hpPercentage ?? hist.hp[hist.hp.length - 1] ?? 0);
      hist.mana.push(lastState.manaPercentage ?? hist.mana[hist.mana.length - 1] ?? 0);
      hist.t.push(new Date().toTimeString().slice(0, 8));
      if (hist.hp.length > HIST_MAX) { hist.hp.shift(); hist.mana.shift(); hist.t.shift(); }
    }
    const active = lastState.pause === false;
    document.getElementById('playBtnTxt').textContent =
      active ? 'Bot pausieren' : 'Bot starten';
    updateDashboard();
  } catch (e) { /* Server kurz weg — naechster Tick */ }
}

async function init() {
  try {
    config = await getJson('/api/config');
    applyDesign(config.design || {});
  } catch (e) { config = null; }
  const hash = location.hash.slice(1);
  if (PAGES.some(page => page[0] === hash)) currentPage = hash;
  renderNav();
  document.getElementById('playBtn').onclick = () =>
    cmd(lastState?.pause === false ? 'pause' : 'play').then(refresh);
  const startAllBtn = document.getElementById('startAllBtn');
  startAllBtn.onclick = async () => {
    startAllBtn.disabled = true;
    startAllBtn.querySelector('span').textContent = 'Startet …';
    await cmd('startAll');
    await refreshStartup();
    setTimeout(() => {
      startAllBtn.disabled = false;
      startAllBtn.querySelector('span').textContent = 'Alles starten';
    }, 4000);
  };
  const stopAllBtn = document.getElementById('stopAllBtn');
  stopAllBtn.onclick = async () => {
    if (!confirm('Alles beenden? Bot, Tibia und OBS werden geschlossen.')) return;
    stopAllBtn.disabled = true;
    stopAllBtn.querySelector('span').textContent = 'Beendet …';
    await cmd('stopAll');
    await refreshStartup();
    setTimeout(() => {
      stopAllBtn.disabled = false;
      stopAllBtn.querySelector('span').textContent = 'Alles beenden';
    }, 4000);
  };
  document.getElementById('updateBtn').onclick = () => {
    if (confirm('PilotNG aktualisieren? Der Bot wird dafür kurz beendet.')) {
      cmd('installUpdate');
    }
  };
  await refresh();
  await refreshStartup();
  renderPage();
  setInterval(refresh, 2000);
  setInterval(refreshStartup, 3000);
  setInterval(async () => {
    if (currentPage === 'dashboard') return;
    try { config = await getJson('/api/config'); } catch (e) { /* egal */ }
  }, 10000);
}

init();
