'use strict';

const $ = id => document.getElementById(id);

let dashData = null;
let speedMode = 'real';
let chartSamples = [];
let chartHoverIdx = null;

function fmtSpeed(bps) {
  if (bps === 0) return '0 bps';
  const units = ['bps', 'Kbps', 'Mbps', 'Gbps'];
  let v = bps;
  let i = 0;
  while (v >= 1000 && i < units.length - 1) { v /= 1000; i++; }
  if (i === 0) return Math.round(v) + ' bps';
  return v.toFixed(1) + ' ' + units[i];
}

function fmtBytes(n) {
  if (n === undefined || n === null) return '—';
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  let i = 0;
  let v = Number(n);
  while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
  return `${v.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
}

function fmtTime(d) {
  return d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}

function countryFlag(cc) {
  if (!cc || cc.length !== 2) return '';
  const base = 0x1F1E6;
  return String.fromCodePoint(base + cc.toUpperCase().charCodeAt(0) - 65) +
         String.fromCodePoint(base + cc.toUpperCase().charCodeAt(1) - 65);
}

function setArc(id, pct, colorVar) {
  const el = $(id);
  if (!el) return;
  const r = 42;
  const c = 2 * Math.PI * r;
  const offset = c - (pct / 100) * c;
  el.style.strokeDasharray = c;
  el.style.strokeDashoffset = offset;
  if (colorVar) el.style.stroke = colorVar;
}

async function api(method, path, body) {
  const opts = { method, headers: body ? { 'Content-Type': 'application/json' } : {} };
  if (body) opts.body = JSON.stringify(body);
  const res = await fetch(path, opts);
  const json = await res.json();
  if (!res.ok || !json.success) throw new Error(json.error || 'HTTP ' + res.status);
  return json.data;
}

const get = path => api('GET', path);
const post = (path, body) => api('POST', path, body);

async function loadData() {
  try {
    dashData = await get('/api/dashboard');
    render();
  } catch (e) {
    console.error('dashboard load error', e);
  }
  $('loading').classList.add('hidden');
}

function render() {
  if (!dashData) return;
  const d = dashData;

  // System
  const cpuPct = d.system.cpu_pct || 0;
  const memPct = d.system.mem_pct || 0;
  setArc('cpuArc', cpuPct, cpuPct > 80 ? 'var(--danger)' : 'var(--accent)');
  setArc('memArc', memPct, memPct > 90 ? 'var(--danger)' : 'var(--success)');
  $('cpuVal').textContent = Math.round(cpuPct) + '%';
  $('memVal').textContent = Math.round(memPct) + '%';

  const sysLines = [];
  if (d.system.mem_total) {
    sysLines.push(fmtBytes(d.system.mem_used) + ' / ' + fmtBytes(d.system.mem_total));
  }
  if (d.system.uptime) {
    const u = d.system.uptime;
    const days = Math.floor(u / 86400);
    const hrs = Math.floor((u % 86400) / 3600);
    const mins = Math.floor((u % 3600) / 60);
    sysLines.push('Uptime: ' + (days ? days + 'д ' : '') + hrs + 'ч ' + mins + 'м');
  }
  $('sysInfo').innerHTML = sysLines.map(l => '<div class="sys-line">' + l + '</div>').join('');

  // Mihomo
  const mihomoRunning = d.mihomo.running;
  const badge = $('mihomoBadge');
  const toggle = $('mihomoToggle');
  const restartBtn = $('mihomoRestartBtn');
  const pidEl = $('mihomoPid');
  if (mihomoRunning) {
    badge.className = 'svc-badge running';
    badge.textContent = 'Запущен';
    pidEl.textContent = 'PID ' + (d.mihomo.pid || '?');
    toggle.checked = true;
    restartBtn.disabled = false;
  } else {
    badge.className = 'svc-badge stopped';
    badge.textContent = 'Остановлен';
    pidEl.textContent = '';
    toggle.checked = false;
    restartBtn.disabled = true;
  }

  // Connectivity Status Banner
  const conn = d.connectivity;
  const banner = $('statusBanner');
  const icon = $('statusIcon');
  const title = $('statusTitle');
  const subtitle = $('statusSubtitle');

  banner.classList.remove('hidden', 'status-online', 'status-warning', 'status-offline');

  if (conn.direct_up) {
    banner.classList.add('status-online');
    icon.innerHTML = '<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg>';
    if (!conn.vpn_up && mihomoRunning) {
      banner.classList.remove('status-online');
      banner.classList.add('status-warning');
      icon.innerHTML = '<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
      title.textContent = 'Интернет есть, но VPN недоступен';
      subtitle.textContent = 'Прямое подключение работает, но VPN-туннель не отвечает';
    } else {
      title.textContent = 'Мы онлайн';
      subtitle.textContent = conn.vpn_up && mihomoRunning ? 'Интернет и VPN работают' : 'Подключение к интернету активно';
    }
  } else {
    banner.classList.add('status-offline');
    icon.innerHTML = '<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
    title.textContent = 'Нет интернет-соединения';
    subtitle.textContent = 'Проверьте подключение к сети';
  }

  // Connectivity timelines
  renderTimeline('directTimeline', conn.minutes_direct, conn.direct_up);
  renderTimeline('vpnTimeline', conn.minutes_vpn, conn.vpn_up);

  // Current connectivity status markers
  $('directStatus').innerHTML = conn.direct_up
    ? '<span class="conn-up">● Доступен ' + (conn.direct_up_since ? conn.direct_up_since : '') + '</span>'
    : '<span class="conn-down">● Недоступен</span>';
  $('vpnStatus').innerHTML = mihomoRunning
    ? (conn.vpn_up ? '<span class="conn-up">● Доступен ' + (conn.vpn_up_since ? conn.vpn_up_since : '') + '</span>' : '<span class="conn-down">● Недоступен</span>')
    : '<span class="conn-na">— Не запущен</span>';

  // Traffic speed
  const samples = speedMode === 'real' ? (d.traffic_real || []) : (d.traffic_avg || []);
  $('gwIface').textContent = d.gateway_iface ? ('Шлюз: ' + d.gateway_iface) : '';

  const lastSample = samples.length > 0 ? samples[samples.length - 1] : null;
  $('rxSpeed').textContent = lastSample ? fmtSpeed(lastSample.rx_bps) : '0 bps';
  $('txSpeed').textContent = lastSample ? fmtSpeed(lastSample.tx_bps) : '0 bps';

  drawChart(samples);

  // IP Info
  renderIP('ipDirect', d.ip_direct);
  renderIP('ipVPN', d.ip_vpn, !mihomoRunning);
}

function renderTimeline(containerId, minutes) {
  const container = $(containerId);
  if (!container) return;

  const n = minutes.length;
  let html = '<div class="tl-bars">';
  for (let i = 0; i < n; i++) {
    const m = minutes[i];
    let cls = 'tl-bar';
    if (m.status === 'up') cls += ' tl-up';
    else if (m.status === 'down') cls += ' tl-down';
    else cls += ' tl-na';

    const minuteTime = new Date(m.minute * 60000);
    const label = minuteTime.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });

    const pingsData = m.pings || {};
    const pingsJson = encodeURIComponent(JSON.stringify(pingsData));
    const dataPingCF = m.ping_cf || '';
    const dataPingGG = m.ping_gg || '';

    html += '<div class="' + cls + '" style="width:calc(100%/' + n + ')" data-time="' + label + '" data-status="' + m.status + '" data-pings="' + pingsJson + '" data-ping-cf="' + dataPingCF + '" data-ping-gg="' + dataPingGG + '"></div>';
  }
  html += '</div>';
  html += '<div class="tl-labels"><span>1ч назад</span><span>45м</span><span>30м</span><span>15м</span><span>Сейчас</span></div>';
  container.innerHTML = html;
}

function drawChart(samples) {
  chartSamples = samples || [];
  const canvas = $('speedChart');
  if (!canvas) return;
  const ctx = canvas.getContext('2d');
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  ctx.scale(dpr, dpr);
  _drawChartContent(ctx, rect.width, rect.height, chartSamples, chartHoverIdx);
}

// Chart inner padding — must match CHART_PAD in event handlers below
const CHART_PAD = { top: 18, right: 20, bottom: 34, left: 72 };

function _drawChartContent(ctx, w, h, samples, hoverIdx) {
  const pad = CHART_PAD;
  const cw = w - pad.left - pad.right;
  const ch = h - pad.top - pad.bottom;

  ctx.clearRect(0, 0, w, h);

  if (!samples || samples.length < 2) {
    ctx.fillStyle = 'rgba(122, 162, 204, 0.4)';
    ctx.font = '13px Inter, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('Нет данных', w / 2, h / 2);
    return;
  }

  let maxVal = 1000;
  for (const s of samples) {
    if (s.rx_bps > maxVal) maxVal = s.rx_bps;
    if (s.tx_bps > maxVal) maxVal = s.tx_bps;
  }
  // Round up to a clean value
  const rawMax = maxVal * 1.2;
  const mag = Math.pow(10, Math.floor(Math.log10(rawMax)));
  maxVal = Math.ceil(rawMax / mag) * mag;

  const timeStart = new Date(samples[0].time).getTime();
  const timeEnd   = new Date(samples[samples.length - 1].time).getTime();
  const timeRange = timeEnd - timeStart || 1;

  function xOf(s) {
    return pad.left + ((new Date(s.time).getTime() - timeStart) / timeRange) * cw;
  }
  function yOf(bps) {
    return pad.top + ch * (1 - bps / maxVal);
  }

  // ── Y-axis grid + labels ──
  const gridLines = 5;
  ctx.font = '10px JetBrains Mono, monospace';
  ctx.textAlign = 'right';
  ctx.textBaseline = 'middle';
  for (let i = 0; i <= gridLines; i++) {
    const y   = pad.top + (ch / gridLines) * i;
    const val = maxVal * (1 - i / gridLines);
    ctx.strokeStyle = i === gridLines ? 'rgba(0,200,255,0.18)' : 'rgba(0,200,255,0.06)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(pad.left, y);
    ctx.lineTo(w - pad.right, y);
    ctx.stroke();
    ctx.fillStyle = 'rgba(122,162,204,0.9)';
    ctx.fillText(fmtSpeed(val), pad.left - 8, y);
  }

  // ── X-axis time labels ──
  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';
  ctx.font = '10px JetBrains Mono, monospace';
  ctx.fillStyle = 'rgba(122,162,204,0.6)';
  const minGap   = 70;
  const pxPerSmp = samples.length > 1 ? cw / (samples.length - 1) : cw;
  const step     = Math.max(1, Math.ceil(minGap / pxPerSmp));
  const shownLbl = new Set();
  for (let i = 0; i < samples.length; i += step) {
    const x = xOf(samples[i]);
    ctx.fillText(fmtTime(new Date(samples[i].time)), x, pad.top + ch + 6);
    shownLbl.add(i);
    ctx.strokeStyle = 'rgba(0,200,255,0.14)';
    ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(x, pad.top + ch); ctx.lineTo(x, pad.top + ch + 4); ctx.stroke();
  }
  // Always show last label if not too close to previous
  const lastIdx = samples.length - 1;
  if (!shownLbl.has(lastIdx)) {
    const lastX  = xOf(samples[lastIdx]);
    const prevI  = Math.floor(lastIdx / step) * step;
    const prevX  = xOf(samples[Math.min(prevI, lastIdx)]);
    if (lastX - prevX > 44) {
      ctx.fillText(fmtTime(new Date(samples[lastIdx].time)), lastX, pad.top + ch + 6);
      ctx.strokeStyle = 'rgba(0,200,255,0.14)';
      ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(lastX, pad.top + ch); ctx.lineTo(lastX, pad.top + ch + 4); ctx.stroke();
    }
  }

  const baseY = pad.top + ch;

  // ── Cardinal-spline path helper (control points clamped to chart bounds) ──
  function smoothPath(pts) {
    if (pts.length < 2) return;
    ctx.moveTo(pts[0].x, pts[0].y);
    const t = 0.2;
    for (let i = 0; i < pts.length - 1; i++) {
      const p0 = pts[Math.max(i - 1, 0)];
      const p1 = pts[i];
      const p2 = pts[i + 1];
      const p3 = pts[Math.min(i + 2, pts.length - 1)];
      const clampY = v => Math.max(pad.top, Math.min(baseY, v));
      ctx.bezierCurveTo(
        p1.x + (p2.x - p0.x) * t, clampY(p1.y + (p2.y - p0.y) * t),
        p2.x - (p3.x - p1.x) * t, clampY(p2.y - (p3.y - p1.y) * t),
        p2.x, p2.y
      );
    }
  }

  const rxPts = samples.map(s => ({ x: xOf(s), y: yOf(s.rx_bps) }));
  const txPts = samples.map(s => ({ x: xOf(s), y: yOf(s.tx_bps) }));

  // ── Clip fills + lines to plot area so bezier curves can't overflow ──
  ctx.save();
  ctx.beginPath();
  ctx.rect(pad.left, pad.top, cw, ch);
  ctx.clip();

  // ── RX fill ──
  const rxGrad = ctx.createLinearGradient(0, pad.top, 0, baseY);
  rxGrad.addColorStop(0, 'rgba(0,212,255,0.22)');
  rxGrad.addColorStop(1, 'rgba(0,212,255,0.01)');
  ctx.beginPath();
  smoothPath(rxPts);
  ctx.lineTo(rxPts[rxPts.length - 1].x, baseY);
  ctx.lineTo(rxPts[0].x, baseY);
  ctx.closePath();
  ctx.fillStyle = rxGrad;
  ctx.fill();

  // ── RX line ──
  ctx.beginPath();
  smoothPath(rxPts);
  ctx.strokeStyle = 'rgba(0,212,255,0.95)';
  ctx.lineWidth = 2; ctx.lineJoin = 'round'; ctx.lineCap = 'round';
  ctx.stroke();

  // ── TX fill ──
  const txGrad = ctx.createLinearGradient(0, pad.top, 0, baseY);
  txGrad.addColorStop(0, 'rgba(0,255,136,0.16)');
  txGrad.addColorStop(1, 'rgba(0,255,136,0.01)');
  ctx.beginPath();
  smoothPath(txPts);
  ctx.lineTo(txPts[txPts.length - 1].x, baseY);
  ctx.lineTo(txPts[0].x, baseY);
  ctx.closePath();
  ctx.fillStyle = txGrad;
  ctx.fill();

  // ── TX line ──
  ctx.beginPath();
  smoothPath(txPts);
  ctx.strokeStyle = 'rgba(0,255,136,0.9)';
  ctx.lineWidth = 2; ctx.lineJoin = 'round'; ctx.lineCap = 'round';
  ctx.stroke();

  ctx.restore(); // end clip

  // ── Legend (top-right, clear of Y-axis labels) ──
  ctx.textAlign = 'left';
  ctx.textBaseline = 'middle';
  ctx.font = '11px Inter, sans-serif';
  const ly = pad.top + 11;
  const lx = w - pad.right - 104;
  ctx.fillStyle = 'rgba(0,212,255,0.9)';  ctx.fillRect(lx,      ly - 1, 14, 2);
  ctx.fillStyle = 'rgba(122,162,204,0.85)'; ctx.fillText('↓ RX', lx + 18, ly);
  ctx.fillStyle = 'rgba(0,255,136,0.9)';  ctx.fillRect(lx + 58, ly - 1, 14, 2);
  ctx.fillStyle = 'rgba(122,162,204,0.85)'; ctx.fillText('↑ TX', lx + 76, ly);

  // ── Hover: crosshair + glowing dots ──
  if (hoverIdx !== null && hoverIdx >= 0 && hoverIdx < samples.length) {
    const s  = samples[hoverIdx];
    const hx = xOf(s);
    const ry = yOf(s.rx_bps);
    const ty = yOf(s.tx_bps);

    // Vertical dashed crosshair
    ctx.save();
    ctx.strokeStyle = 'rgba(255,255,255,0.18)';
    ctx.lineWidth = 1;
    ctx.setLineDash([4, 4]);
    ctx.beginPath(); ctx.moveTo(hx, pad.top); ctx.lineTo(hx, baseY); ctx.stroke();
    ctx.setLineDash([]);
    ctx.restore();

    // Horizontal guide lines to Y-axis
    ctx.save();
    ctx.lineWidth = 1;
    ctx.setLineDash([2, 5]);
    ctx.strokeStyle = 'rgba(0,212,255,0.2)';
    ctx.beginPath(); ctx.moveTo(pad.left, ry); ctx.lineTo(hx, ry); ctx.stroke();
    ctx.strokeStyle = 'rgba(0,255,136,0.18)';
    ctx.beginPath(); ctx.moveTo(pad.left, ty); ctx.lineTo(hx, ty); ctx.stroke();
    ctx.setLineDash([]);
    ctx.restore();

    // RX dot — outer glow
    ctx.beginPath(); ctx.arc(hx, ry, 8, 0, Math.PI * 2);
    ctx.fillStyle = 'rgba(0,212,255,0.15)'; ctx.fill();
    // RX dot — core
    ctx.beginPath(); ctx.arc(hx, ry, 4.5, 0, Math.PI * 2);
    ctx.fillStyle = '#00d4ff'; ctx.fill();
    ctx.strokeStyle = 'rgba(10,14,26,0.9)'; ctx.lineWidth = 1.5; ctx.stroke();

    // TX dot — outer glow
    ctx.beginPath(); ctx.arc(hx, ty, 8, 0, Math.PI * 2);
    ctx.fillStyle = 'rgba(0,255,136,0.12)'; ctx.fill();
    // TX dot — core
    ctx.beginPath(); ctx.arc(hx, ty, 4.5, 0, Math.PI * 2);
    ctx.fillStyle = '#00ff88'; ctx.fill();
    ctx.strokeStyle = 'rgba(10,14,26,0.9)'; ctx.lineWidth = 1.5; ctx.stroke();
  }
}

function renderIP(containerId, info, disabled) {
  const el = $(containerId);
  if (!el) return;

  if (disabled) {
    el.innerHTML = '<div class="ip-address ip-na">Mihomo не запущен</div><div class="ip-country"></div>';
    return;
  }

  if (!info) {
    el.innerHTML = '<div class="ip-address ip-loading">Определение...</div><div class="ip-country"></div>';
    return;
  }

  const flag = info.cc ? countryFlag(info.cc) + ' ' : '';
  el.innerHTML = '<div class="ip-address">' + info.ip + '</div>' +
    '<div class="ip-country">' + flag + (info.country || '') + '</div>';
}

// Mihomo controls
$('mihomoToggle').addEventListener('change', async (e) => {
  const action = e.target.checked ? 'start' : 'stop';
  try {
    await post('/api/mihomo/' + action, null);
    showToast('Mihomo ' + (action === 'start' ? 'запущен' : 'остановлен'), 'success');
    await loadData();
  } catch (err) {
    showToast('Ошибка: ' + err.message, 'error');
    await loadData();
  }
});

$('mihomoRestartBtn').addEventListener('click', async () => {
  try {
    await post('/api/mihomo/restart', null);
    showToast('Mihomo перезапущен', 'success');
    await loadData();
  } catch (e) {
    showToast('Ошибка: ' + e.message, 'error');
  }
});

// Speed mode switch
$('speedModeSwitch').addEventListener('click', (e) => {
  const btn = e.target.closest('.seg-btn');
  if (!btn) return;
  speedMode = btn.dataset.mode;
  $('speedModeSwitch').querySelectorAll('.seg-btn').forEach(b => b.classList.remove('active'));
  btn.classList.add('active');
  if (dashData) render();
});

// Connectivity settings modal
function openConnSettings() {
  const conn = dashData ? dashData.connectivity : null;
  const defaultDirect = [
    { name: 'Cloudflare', url: 'http://cp.cloudflare.com/generate_204' },
    { name: 'Google', url: 'http://connectivitycheck.gstatic.com/generate_204' },
  ];
  const direct = (conn && conn.endpoint_names) ? [] : defaultDirect;

  if (dashData && dashData.connectivity) {
    fetch('/api/config.yaml', { method: 'GET' })
      .then(r => r.json())
      .then(j => {
        const c = j.data && j.data.connectivity ? j.data.connectivity : {};
        const d = c.direct || defaultDirect;
        const v = c.via_proxy || defaultDirect;
        $('direct1Name').value = (d[0] && d[0].name) || '';
        $('direct1Url').value = (d[0] && d[0].url) || '';
        $('direct2Name').value = (d[1] && d[1].name) || '';
        $('direct2Url').value = (d[1] && d[1].url) || '';
        $('proxy1Name').value = (v[0] && v[0].name) || '';
        $('proxy1Url').value = (v[0] && v[0].url) || '';
        $('proxy2Name').value = (v[1] && v[1].name) || '';
        $('proxy2Url').value = (v[1] && v[1].url) || '';
      })
      .catch(() => {
        $('direct1Name').value = defaultDirect[0].name;
        $('direct1Url').value = defaultDirect[0].url;
        $('direct2Name').value = defaultDirect[1].name;
        $('direct2Url').value = defaultDirect[1].url;
        $('proxy1Name').value = defaultDirect[0].name;
        $('proxy1Url').value = defaultDirect[0].url;
        $('proxy2Name').value = defaultDirect[1].name;
        $('proxy2Url').value = defaultDirect[1].url;
      });
  } else {
    $('direct1Name').value = defaultDirect[0].name;
    $('direct1Url').value = defaultDirect[0].url;
    $('direct2Name').value = defaultDirect[1].name;
    $('direct2Url').value = defaultDirect[1].url;
    $('proxy1Name').value = defaultDirect[0].name;
    $('proxy1Url').value = defaultDirect[0].url;
    $('proxy2Name').value = defaultDirect[1].name;
    $('proxy2Url').value = defaultDirect[1].url;
  }
  $('connModal').classList.remove('hidden');
}

function closeConnSettings() {
  $('connModal').classList.add('hidden');
}

async function saveConnSettings(e) {
  if (e) e.preventDefault();

  const endpoints = {
    direct: [
      { name: $('direct1Name').value.trim(), url: $('direct1Url').value.trim() },
      { name: $('direct2Name').value.trim(), url: $('direct2Url').value.trim() },
    ].filter(ep => ep.name && ep.url),
    via_proxy: [
      { name: $('proxy1Name').value.trim(), url: $('proxy1Url').value.trim() },
      { name: $('proxy2Name').value.trim(), url: $('proxy2Url').value.trim() },
    ].filter(ep => ep.name && ep.url),
  };

  if (endpoints.direct.length === 0) {
    showToast('Укажите хотя бы одну точку для прямого подключения', 'error');
    return;
  }
  if (endpoints.via_proxy.length === 0) {
    showToast('Укажите хотя бы одну точку для проверки через прокси', 'error');
    return;
  }

  try {
    const cfgRes = await get('/api/config.yaml');
    cfgRes.connectivity = endpoints;
    const resp = await fetch('/api/config.yaml', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(cfgRes),
    });
    const j = await resp.json();
    if (!j.success) throw new Error(j.error || 'save failed');
    showToast('Настройки подключения сохранены', 'success');
    closeConnSettings();
  } catch (err) {
    showToast('Ошибка: ' + err.message, 'error');
  }
}

$('connSettingsBtn').addEventListener('click', openConnSettings);
$('closeConnModal').addEventListener('click', closeConnSettings);
$('cancelConnSettings').addEventListener('click', closeConnSettings);
$('connModalBackdrop').addEventListener('click', closeConnSettings);
$('connSettingsForm').addEventListener('submit', saveConnSettings);


let toastTimer;
function showToast(msg, type) {
  const t = $('toast');
  t.textContent = msg;
  t.className = 'toast ' + type;
  t.classList.remove('hidden');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => t.classList.add('hidden'), 3500);
}

// Resize chart
window.addEventListener('resize', () => {
  if (dashData) {
    const samples = speedMode === 'real' ? dashData.traffic_real : dashData.traffic_avg;
    drawChart(samples || []);
  }
});

// Auto-refresh
setInterval(loadData, 500);

// ── Custom timeline tooltip ──
const tlTooltip = document.createElement('div');
tlTooltip.className = 'tl-tooltip';
tlTooltip.style.left = '-9999px';
tlTooltip.style.top = '-9999px';
document.body.appendChild(tlTooltip);

// ── Chart hover tooltip ──
const chartTooltip = document.createElement('div');
chartTooltip.className = 'chart-tooltip';
document.body.appendChild(chartTooltip);

let tlTooltipTimer = null;
let tlCursorX = 0;
let tlCursorY = 0;

document.addEventListener('mousemove', (e) => {
  const zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;
  tlCursorX = e.clientX / zoom;
  tlCursorY = e.clientY / zoom;
  const bar = e.target.closest('.tl-bar');
  if (!bar) {
    tlTooltip.classList.remove('visible');
    return;
  }
  if (tlTooltip.classList.contains('visible')) positionTooltip();
});

document.addEventListener('mouseover', (e) => {
  const bar = e.target.closest('.tl-bar');
  if (!bar) return;

  const time = bar.dataset.time;
  const status = bar.dataset.status;

  let statusText, dotClass;
  if (status === 'up') { statusText = 'Доступен'; dotClass = 'up'; }
  else if (status === 'down') { statusText = 'Недоступен'; dotClass = 'down'; }
  else { statusText = 'Нет данных'; dotClass = 'na'; }

  let pingHtml = '';
  if (status === 'up') {
    const parts = [];
    const pingsData = bar.dataset.pings;
    if (pingsData) {
      try {
        const pings = JSON.parse(decodeURIComponent(pingsData));
        for (const [name, ms] of Object.entries(pings)) {
          if (ms > 0) parts.push(name + ': ' + ms + ' мс');
        }
      } catch (_) {
        const pingCF = bar.dataset.pingCf;
        const pingGG = bar.dataset.pingGg;
        if (pingCF) parts.push('CF: ' + pingCF + ' мс');
        if (pingGG) parts.push('Google: ' + pingGG + ' мс');
      }
    } else {
      const pingCF = bar.dataset.pingCf;
      const pingGG = bar.dataset.pingGg;
      if (pingCF) parts.push('CF: ' + pingCF + ' мс');
      if (pingGG) parts.push('Google: ' + pingGG + ' мс');
    }
    if (parts.length) pingHtml = '<span class="tl-tooltip-ping">' + parts.join(', ') + '</span>';
  }

  tlTooltip.innerHTML =
    '<div class="tl-tooltip-time">' + time + '</div>' +
    '<div class="tl-tooltip-row"><span class="tl-tooltip-dot ' + dotClass + '"></span>' + statusText + pingHtml + '</div>';

  void tlTooltip.offsetWidth;
  positionTooltip();
  tlTooltip.classList.add('visible');
  clearTimeout(tlTooltipTimer);
});

document.addEventListener('mouseout', (e) => {
  const bar = e.target.closest('.tl-bar');
  if (!bar) return;
  const related = e.relatedTarget;
  if (related && related.closest && related.closest('.tl-bar')) return;
  tlTooltipTimer = setTimeout(() => tlTooltip.classList.remove('visible'), 80);
});

function positionTooltip() {
  const tw = tlTooltip.offsetWidth;
  const th = tlTooltip.offsetHeight;
  let left = tlCursorX - tw / 2;
  let top = tlCursorY - th - 12;
  if (top < 4) top = tlCursorY + 16;
  if (left < 4) left = 4;
  if (left + tw > window.innerWidth - 4) left = window.innerWidth - tw - 4;
  tlTooltip.style.left = left + 'px';
  tlTooltip.style.top = top + 'px';
}

// ── Chart hover / tooltip logic ──
function hideChartTooltip() {
  chartTooltip.classList.remove('visible');
  if (chartHoverIdx !== null) {
    chartHoverIdx = null;
    redrawChartHover();
  }
}

function redrawChartHover() {
  const canvas = $('speedChart');
  if (!canvas || !chartSamples || chartSamples.length < 2) return;
  const ctx = canvas.getContext('2d');
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  canvas.width  = rect.width  * dpr;
  canvas.height = rect.height * dpr;
  ctx.scale(dpr, dpr);
  _drawChartContent(ctx, rect.width, rect.height, chartSamples, chartHoverIdx);
}

$('speedChart').addEventListener('mousemove', (e) => {
  if (!chartSamples || chartSamples.length < 2) { hideChartTooltip(); return; }

  const canvas = $('speedChart');
  const rect   = canvas.getBoundingClientRect();
  const mx     = e.clientX - rect.left;   // offset within canvas in clientX pixels
  const pad    = CHART_PAD;
  const cw     = rect.width - pad.left - pad.right;

  if (mx < pad.left - 6 || mx > rect.width - pad.right + 6) {
    hideChartTooltip(); return;
  }

  const timeStart = new Date(chartSamples[0].time).getTime();
  const timeEnd   = new Date(chartSamples[chartSamples.length - 1].time).getTime();
  const timeRange = timeEnd - timeStart || 1;

  let closestIdx = 0, closestDist = Infinity;
  for (let i = 0; i < chartSamples.length; i++) {
    const sx = pad.left + ((new Date(chartSamples[i].time).getTime() - timeStart) / timeRange) * cw;
    const d  = Math.abs(mx - sx);
    if (d < closestDist) { closestDist = d; closestIdx = i; }
  }

  if (closestIdx !== chartHoverIdx) {
    chartHoverIdx = closestIdx;
    redrawChartHover();
  }

  // Position tooltip above the hovered data point
  const s   = chartSamples[closestIdx];
  const sx  = pad.left + ((new Date(s.time).getTime() - timeStart) / timeRange) * cw;
  const zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;

  chartTooltip.innerHTML =
    '<div class="chart-tt-time">' + fmtTime(new Date(s.time)) + '</div>' +
    '<div class="chart-tt-row"><span class="chart-tt-dot rx"></span>' +
      '<span class="chart-tt-label">↓ RX</span>' +
      '<span class="chart-tt-val">' + fmtSpeed(s.rx_bps) + '</span></div>' +
    '<div class="chart-tt-row"><span class="chart-tt-dot tx"></span>' +
      '<span class="chart-tt-label">↑ TX</span>' +
      '<span class="chart-tt-val">' + fmtSpeed(s.tx_bps) + '</span></div>';

  const ttW = chartTooltip.offsetWidth;
  const ttH = chartTooltip.offsetHeight;
  const anchorX = (rect.left + sx) / zoom;
  const anchorY = rect.top / zoom;
  let left = anchorX - ttW / 2;
  let top  = anchorY - ttH - 12;
  if (top  < 4) top  = (rect.bottom / zoom) + 8;
  if (left < 4) left = 4;
  if (left + ttW > window.innerWidth / zoom - 4) left = window.innerWidth / zoom - ttW - 4;
  chartTooltip.style.left = left + 'px';
  chartTooltip.style.top  = top  + 'px';
  chartTooltip.classList.add('visible');
});

$('speedChart').addEventListener('mouseleave', hideChartTooltip);

(async () => {
  try {
    const status = await get('/api/auth/status');
    if (status.default_password) {
      $('defaultPwWarning').classList.remove('hidden');
    }
  } catch (_) {}
  await loadData();
})();