(() => {
  'use strict';

const BASE_API = new URL('api/', window.location.href).href;
const ACCESS = Object.assign({
  counts:true, routers:true, sectors:true, receivers:true, receiverRefresh:true,
  traffic:true, billing:true, networkAlerts:true, securityAlerts:true, securityMonitor:true
}, window.NOC_DASHBOARD_ACCESS || {});

const URLS = {
  counts: BASE_API + 'noc_counts.php',
  routers: BASE_API + 'noc_routers_card.php',
  sectors: BASE_API + 'noc_sectors_card.php',
  receivers: BASE_API + 'noc_receivers_card.php',
  receiverRefresh: BASE_API + 'receiver_match_refresh.php',
  traffic: BASE_API + 'noc_traffic_card.php',
  billingAlerts: BASE_API + 'billing_alerts.php',
  networkAlerts: BASE_API + 'subscriber_network_alerts.php',
  securityAlerts: BASE_API + 'security_alerts.php',
  securityMonitor: BASE_API + 'security_monitor.php?summary=1&warmup=0',
  billingApprove: BASE_API + 'billing_approve.php',
  messageQueueStatus: BASE_API + 'message_queue_status.php',
  messageQueueRun: BASE_API + 'message_queue_run.php'
};
  const UI_REFRESH_MS = 12000;
  const SECONDARY_REFRESH_MS = 90000;
  const DATA_REFRESH_MS = 25000;
  const TRAFFIC_REFRESH_MS = 6000;
  const TRAFFIC_UI_MS = 1200;
  const MAX_HISTORY = 28;

  const state = {
    counts: null,
    routers: null,
    sectors: null,
    receivers: null,
    traffic: null,
    billing: null,
    networkAlerts: null,
    securityAlerts: null,
    securityMonitor: null,
    history: { total: [], rx: [], tx: [] },
    sourceHistory: new Map(),
    trafficVisual: { ready: false, rx: 0, tx: 0, total: 0 },
    receiverAutoRefresh: { attempted: false, running: false },
    lastFetched: {}, inFlight: {}
  };

  const alertLineCss = `
    .nocx-alert-line{position:relative;display:flex;align-items:center;gap:10px;min-height:38px;padding:9px 46px 9px 10px;margin:0 0 8px;border-radius:0;border:0;background:transparent!important;color:inherit;text-decoration:none!important;box-shadow:none;overflow:visible}
    html[dir="ltr"] .nocx-alert-line{padding:9px 10px 9px 46px}
    .nocx-alert-line::after{content:"";position:absolute;left:10px;right:10px;bottom:-4px;height:1px;background:linear-gradient(90deg,transparent,rgba(243,186,47,.92),transparent);box-shadow:0 0 10px rgba(243,186,47,.45);pointer-events:none}
    .nocx-alert-line:last-child::after{display:none}
    .nocx-alert-line__main{display:flex;align-items:center;gap:8px;min-width:0;flex:1;white-space:nowrap;overflow:hidden}
    .nocx-alert-line__count{display:inline-flex;align-items:center;justify-content:center;min-width:23px;height:23px;padding:0 7px;border-radius:999px;background:rgba(255,216,98,.14);color:#ffd96a;border:1px solid rgba(255,216,98,.22);font-weight:950;font-size:12px;flex:0 0 auto}
    .nocx-alert-line__title{white-space:nowrap;font-size:12.5px;font-weight:950;color:#fff;flex:0 0 auto}
    .nocx-alert-line__text{font-size:11.5px;font-weight:800;color:rgba(232,244,255,.76);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
    .nocx-alert-line__close{position:absolute;inset-inline-start:10px;top:50%;transform:translateY(-50%);width:25px;height:25px;min-width:25px;padding:0;border:0;border-radius:7px;background:rgba(255,255,255,.055);color:rgba(232,244,255,.86);font-size:0;line-height:0;overflow:hidden;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:.16s ease}
    .nocx-alert-line__close::before,.nocx-alert-line__close::after{content:"";position:absolute;left:50%;top:50%;width:11px;height:1.7px;border-radius:2px;background:currentColor;transform-origin:center}
    .nocx-alert-line__close::before{transform:translate(-50%,-50%) rotate(45deg)}.nocx-alert-line__close::after{transform:translate(-50%,-50%) rotate(-45deg)}
    .nocx-alert-line__close:hover{background:#e81123;color:#fff}
    .nocx-alert-line.warn .nocx-alert-line__title{color:#ffe69b}.nocx-alert-line.danger .nocx-alert-line__title{color:#ffb6c4}.nocx-alert-line.info .nocx-alert-line__title{color:#bfeeff}
  `;
  if (!document.getElementById('nocx-alert-line-style')) {
    const style = document.createElement('style');
    style.id = 'nocx-alert-line-style';
    style.textContent = alertLineCss;
    document.head.appendChild(style);
  }

  const $ = (id) => document.getElementById(id);
  const num = (v, d = 0) => { const n = parseFloat(v); return Number.isFinite(n) ? n : d; };
  const esc = (v) => String(v ?? '').replace(/[&<>'"]/g, (ch) => ({ '&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;' }[ch]));
  const fmt1 = (v) => num(v, 0).toFixed(1);
  const fmtRate = (v) => {
    const n = Math.max(0, num(v, 0));
    if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1) + ' Gbps';
    return n.toFixed(n >= 100 ? 0 : 1) + ' Mbps';
  };
  const setText = (id, value) => { const el = $(id); if (el) el.textContent = value; };
  function showFloatingMessage(message, type = 'info') {
    const text = String(message || '').trim();
    if (!text) return;
    try {
      if (typeof window.uaToast === 'function') { window.uaToast(text, type); return; }
      if (typeof window.UAToast === 'function') { window.UAToast(text, type); return; }
    } catch (_) {}
    let stack = document.getElementById('nocxFloatingToastStack');
    if (!stack) {
      stack = document.createElement('div');
      stack.id = 'nocxFloatingToastStack';
      stack.style.cssText = 'position:fixed;z-index:99999;top:18px;left:18px;display:flex;flex-direction:column;gap:8px;max-width:min(420px,calc(100vw - 36px));pointer-events:none';
      document.body.appendChild(stack);
    }
    const el = document.createElement('div');
    const bg = type === 'success' ? 'rgba(19,148,88,.96)' : (type === 'error' ? 'rgba(190,45,62,.96)' : 'rgba(36,105,180,.96)');
    el.textContent = text;
    el.style.cssText = 'pointer-events:auto;border:1px solid rgba(255,255,255,.18);box-shadow:0 14px 35px rgba(0,0,0,.38);background:' + bg + ';color:#fff;border-radius:14px;padding:11px 14px;font:800 12.5px Tahoma,Arial,sans-serif;line-height:1.7;opacity:0;transform:translateY(-8px);transition:.18s ease';
    stack.appendChild(el);
    requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateY(0)'; });
    setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateY(-8px)'; setTimeout(() => el.remove(), 220); }, type === 'error' ? 6200 : 4200);
  }

  async function fetchJson(url) {
    const res = await fetch(url + (url.includes('?') ? '&' : '?') + '_=' + Date.now(), { cache: 'no-store', credentials: 'same-origin' });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    const text = await res.text();
    return JSON.parse(text.replace(/^\uFEFF/, ''));
  }

  function shouldFetch(key, now = Date.now()) {
    const ttl = key === 'traffic' ? TRAFFIC_REFRESH_MS : DATA_REFRESH_MS;
    return !state.lastFetched[key] || (now - state.lastFetched[key]) >= ttl;
  }

  function canPollDashboard() {
    return !document.hidden;
  }

  async function softFetch(key, url) {
    if (ACCESS[key] === false || !canPollDashboard() || state.inFlight[key] || !shouldFetch(key)) return;
    state.inFlight[key] = true;
    state.lastFetched[key] = Date.now();
    try {
      const payload = await fetchJson(url);
      if (payload && typeof payload === 'object') { state[key] = payload; renderAll(); }
    } catch (_) {
    } finally {
      state.inFlight[key] = false;
    }
  }

  function meterClass(value) {
    const v = num(value, 0);
    if (v >= 85) return 'danger';
    if (v >= 65) return 'warn';
    return 'ok';
  }
  function statusClass(stateText) {
    const s = String(stateText || '').toUpperCase();
    if (s === 'ONLINE' || s === 'UP') return 'online';
    if (s === 'PARTIAL' || s === 'ONLINE_PARTIAL') return 'partial';
    if (s === 'WARN' || s === 'WARNING') return 'warn';
    return 'offline';
  }
  function statusLabel(stateText) {
    const s = String(stateText || '').toUpperCase();
    if (s === 'ONLINE' || s === 'UP') return 'ONLINE';
    if (s === 'PARTIAL' || s === 'ONLINE_PARTIAL') return 'PARTIAL';
    if (s === 'WARN' || s === 'WARNING') return 'API WARN';
    return 'OFFLINE';
  }
  function routerStatusTitle(router) {
    const note = String(router?.state_note || '');
    if (note === 'api_unavailable_not_router_offline') return 'تعذر التحقق عبر RouterOS API؛ هذا لا يعني أن الراوتر منقطع.';
    if (note === 'rechecking_after_probe_failure') return 'فشل فحص عابر؛ جارٍ إعادة التحقق قبل اعتماد الانقطاع.';
    if (note === 'confirmed_after_retries') return 'تعذر الوصول بعد عدة فحوص مستقلة ومتتالية.';
    return '';
  }
  function renderMeter(value) {
    const v = Math.max(0, Math.min(100, Math.round(num(value, 0))));
    const cls = meterClass(v);
    return `<div class="nocx-meter ${cls}"><div class="nocx-meter__bar"><i style="width:${v}%"></i></div><strong>${v}%</strong></div>`;
  }

  function drawMainChart() {
    const canvas = $('trafficCanvas');
    if (!canvas) return;

    const dpr = Math.max(1, window.devicePixelRatio || 1);
    const box = canvas.parentElement?.getBoundingClientRect();
    const width = Math.max(480, Math.floor((box?.width || canvas.clientWidth || 900) * dpr));
    const height = Math.max(220, Math.floor((box?.height || canvas.clientHeight || 250) * dpr));
    if (canvas.width !== width || canvas.height !== height) {
      canvas.width = width;
      canvas.height = height;
    }

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    ctx.clearRect(0, 0, width, height);
    ctx.save();

    const pad = { top: 20 * dpr, right: 18 * dpr, bottom: 30 * dpr, left: 44 * dpr };
    const innerW = width - pad.left - pad.right;
    const innerH = height - pad.top - pad.bottom;

    const rx = state.history.rx || [];
    const tx = state.history.tx || [];
    const total = state.history.total || [];
    const capacity = Math.max(1, num(state.traffic?.iface?.max_mbps, 0));
    const dataMax = Math.max(8, ...total, ...rx, ...tx, capacity);
    const maxVal = Math.max(8, dataMax * 1.16);

    const chartX = pad.left;
    const chartY = pad.top;
    const chartW = innerW;
    const chartH = innerH;
    const bottom = chartY + chartH;
    const right = chartX + chartW;

    const toX = (i, len) => chartX + (chartW / Math.max(1, len - 1)) * i;
    const toY = (v) => bottom - (Math.max(0, num(v, 0)) / maxVal) * chartH;

    const makePoints = (values) => values.map((v, i) => ({ x: toX(i, values.length), y: toY(v), v: num(v, 0) }));

    const drawSmoothPath = (points) => {
      if (!points.length) return;
      ctx.beginPath();
      ctx.moveTo(points[0].x, points[0].y);
      for (let i = 1; i < points.length; i++) {
        const p0 = points[i - 1];
        const p1 = points[i];
        const midX = (p0.x + p1.x) / 2;
        ctx.bezierCurveTo(midX, p0.y, midX, p1.y, p1.x, p1.y);
      }
    };

    const drawBackground = () => {
      const bg = ctx.createLinearGradient(0, chartY, 0, bottom);
      bg.addColorStop(0, 'rgba(74,184,255,.025)');
      bg.addColorStop(.55, 'rgba(255,180,65,.018)');
      bg.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = bg;
      ctx.fillRect(chartX, chartY, chartW, chartH);

      ctx.lineWidth = 1 * dpr;
      ctx.font = `${10.5 * dpr}px Tahoma, Arial, sans-serif`;
      ctx.textBaseline = 'middle';

      for (let i = 0; i <= 4; i++) {
        const y = chartY + (chartH / 4) * i;
        ctx.beginPath();
        ctx.moveTo(chartX, y);
        ctx.lineTo(right, y);
        ctx.strokeStyle = i === 4 ? 'rgba(255,255,255,.075)' : 'rgba(170,205,255,.045)';
        ctx.stroke();

        const label = ((maxVal * (4 - i)) / 4).toFixed(0);
        ctx.fillStyle = 'rgba(196,216,255,.52)';
        ctx.fillText(label, 7 * dpr, y + .5 * dpr);
      }

      const verticals = Math.min(7, Math.max(2, Math.max(rx.length, tx.length)));
      for (let i = 0; i < verticals; i++) {
        const x = chartX + (chartW / Math.max(1, verticals - 1)) * i;
        ctx.beginPath();
        ctx.moveTo(x, chartY);
        ctx.lineTo(x, bottom);
        ctx.strokeStyle = 'rgba(170,205,255,.028)';
        ctx.stroke();
      }

      if (capacity > 0 && capacity < maxVal) {
        const y = toY(capacity);
        ctx.save();
        ctx.setLineDash([6 * dpr, 8 * dpr]);
        ctx.beginPath();
        ctx.moveTo(chartX, y);
        ctx.lineTo(right, y);
        ctx.strokeStyle = 'rgba(255,210,112,.34)';
        ctx.lineWidth = 1 * dpr;
        ctx.stroke();
        ctx.setLineDash([]);
        ctx.fillStyle = 'rgba(255,218,128,.72)';
        ctx.font = `${10.5 * dpr}px Tahoma, Arial, sans-serif`;
        ctx.textAlign = 'right';
        ctx.fillText(`السعة ${capacity.toFixed(0)} Mbps`, right - 2 * dpr, y - 10 * dpr);
        ctx.restore();
      }
    };

    const drawSeries = (values, stroke, glow, fillTop, fillBottom, dotFill) => {
      if (!values.length) return;
      const points = makePoints(values);

      drawSmoothPath(points);
      ctx.lineTo(points[points.length - 1].x, bottom);
      ctx.lineTo(points[0].x, bottom);
      ctx.closePath();
      const fill = ctx.createLinearGradient(0, chartY, 0, bottom);
      fill.addColorStop(0, fillTop);
      fill.addColorStop(.62, fillBottom);
      fill.addColorStop(1, 'rgba(0,0,0,0)');
      ctx.fillStyle = fill;
      ctx.fill();

      drawSmoothPath(points);
      ctx.lineWidth = 4.6 * dpr;
      ctx.strokeStyle = glow;
      ctx.globalAlpha = .28;
      ctx.shadowColor = stroke;
      ctx.shadowBlur = 18 * dpr;
      ctx.stroke();

      drawSmoothPath(points);
      ctx.globalAlpha = 1;
      ctx.shadowColor = stroke;
      ctx.shadowBlur = 10 * dpr;
      ctx.lineWidth = 2.35 * dpr;
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
      ctx.strokeStyle = stroke;
      ctx.stroke();
      ctx.shadowBlur = 0;

      const last = points[points.length - 1];
      if (last) {
        ctx.beginPath();
        ctx.arc(last.x, last.y, 5.2 * dpr, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(3,12,24,.92)';
        ctx.fill();
        ctx.lineWidth = 2.4 * dpr;
        ctx.strokeStyle = stroke;
        ctx.shadowColor = stroke;
        ctx.shadowBlur = 12 * dpr;
        ctx.stroke();
        ctx.shadowBlur = 0;

        ctx.beginPath();
        ctx.arc(last.x, last.y, 2.2 * dpr, 0, Math.PI * 2);
        ctx.fillStyle = dotFill;
        ctx.fill();
      }
    };

    drawBackground();
    drawSeries(rx, '#46d7ff', 'rgba(70,215,255,.75)', 'rgba(70,215,255,.20)', 'rgba(70,215,255,.050)', '#9bf0ff');
    drawSeries(tx, '#ffbd4a', 'rgba(255,189,74,.68)', 'rgba(255,189,74,.16)', 'rgba(255,189,74,.040)', '#ffd982');

    ctx.textAlign = 'left';
    ctx.textBaseline = 'alphabetic';
    ctx.font = `${10.5 * dpr}px Tahoma, Arial, sans-serif`;
    ctx.fillStyle = 'rgba(210,228,255,.66)';
    ctx.fillText('Mbps', 7 * dpr, chartY - 6 * dpr);

    ctx.restore();
  }

  function drawMiniSpark(canvas, values) {
    if (!canvas) return;
    const dpr = Math.max(1, window.devicePixelRatio || 1);
    const box = canvas.getBoundingClientRect();
    const width = Math.max(120, Math.floor((box.width || 160) * dpr));
    const height = Math.max(34, Math.floor((box.height || 40) * dpr));
    if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; }
    const ctx = canvas.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, width, height); if (!values?.length) return;
    const maxVal = Math.max(2, ...values) * 1.15; const pad = 4 * dpr;
    ctx.beginPath(); values.forEach((v, i) => { const x = pad + ((width - pad * 2) / Math.max(1, values.length - 1)) * i; const y = height - pad - (num(v, 0) / maxVal) * (height - pad * 2); if (!i) ctx.moveTo(x, y); else ctx.lineTo(x, y); });
    ctx.strokeStyle = '#4ab8ff'; ctx.lineWidth = 2 * dpr; ctx.stroke();
  }

  function renderRouters() {
    const list = $('routerList');
    const payload = state.routers || { items: [], online: 0, total: 0 };
    const routers = Array.isArray(payload.items) ? payload.items : [];
    setText('routersOnlineCount', payload.online ?? 0);
    setText('routersTotalCount', payload.total ?? routers.length);
    setText('summaryRoutersOnline', payload.online ?? 0);
    setText('nocRoutersTop', (payload.online ?? 0) + '/' + (payload.total ?? routers.length));
    const avgCpu = routers.length ? Math.round(routers.reduce((sum, r) => sum + num(r.cpu_pct, 0), 0) / routers.length) : 0;
    const avgRam = routers.length ? Math.round(routers.reduce((sum, r) => sum + num(r.ram_pct, 0), 0) / routers.length) : 0;
    setText('nocCpuTop', avgCpu + '%');
    setText('nocRamTop', avgRam + '%');
    if (!list) return;
    if (!routers.length) { list.innerHTML = '<div class="nocx-empty">بانتظار قراءة الراوترات...</div>'; return; }
    const temperatureBadge = (r) => {
      const raw = r?.temperature_c;
      const value = raw === null || raw === undefined || raw === '' ? null : Number(raw);
      if (!Number.isFinite(value)) {
        return '<span class="nocx-router-temperature is-unavailable" title="الراوتر لا يوفر حساس حرارة"><i class="fa fa-thermometer-empty" aria-hidden="true"></i><b>—</b></span>';
      }
      const tone = value >= 70 ? ' is-hot' : (value >= 55 ? ' is-warm' : '');
      const shown = Math.round(value * 10) / 10;
      const sensor = r.temperature_sensor ? ` · ${String(r.temperature_sensor).replace(/-/g, ' ')}` : '';
      return `<span class="nocx-router-temperature${tone}" title="حرارة الراوتر${esc(sensor)}"><i class="fa fa-thermometer-half" aria-hidden="true"></i><b>${esc(shown)}</b><small>°C</small></span>`;
    };
    list.innerHTML = routers.map((r) => `
      <article class="nocx-router-item">
        <div class="nocx-router-head">
          <div class="nocx-router-name">
            <strong>${esc(r.name || r.identity || 'Router')}</strong>
            <small>${esc(r.model || '—')} · ${esc(r.host || '—')}</small>
          </div>
          <div class="nocx-router-head-state">
            ${temperatureBadge(r)}
            <span class="nocx-status-badge ${statusClass(r.state)}" title="${esc(routerStatusTitle(r))}">${statusLabel(r.state)}</span>
          </div>
        </div>
        <div class="nocx-router-meta">
          <div class="nocx-router-meta-item"><span>CPU</span><strong>${renderMeter(r.cpu_pct)}</strong></div>
          <div class="nocx-router-meta-item"><span>RAM</span><strong>${renderMeter(r.ram_pct)}</strong></div>
          <div class="nocx-router-meta-item"><span>جلسات/حمل</span><strong>${esc(r.ppp_active ?? '—')}</strong></div>
          <div class="nocx-router-meta-item"><span>Uptime</span><strong>${esc(r.uptime || '—')}</strong></div>
        </div>
      </article>`).join('');
  }

  function onlineish(item) {
    if (!item || typeof item !== 'object') return false;
    const s = String(item.state || '').toUpperCase();
    if (s === 'ONLINE' || s === 'UP') return true;
    return item.cpu !== null || item.memory !== null || item.quality !== null || item.capacity !== null || item.model;
  }

  function statusClassForItem(item) {
    return onlineish(item) ? 'online' : statusClass(item.state);
  }
  function statusLabelForItem(item) {
    return onlineish(item) ? 'ONLINE' : statusLabel(item.state);
  }

  function airmaxRowSector(item) {
    return `
      <tr>
        <td><div class="nocx-host-cell"><strong>${esc(item.name || '—')}</strong><small>${esc(item.host || '—')}</small></div></td>
        <td>${esc(item.model || '—')}</td>
        <td>${item.cpu !== null && item.cpu !== undefined ? renderMeter(item.cpu) : '—'}</td>
        <td>${item.memory !== null && item.memory !== undefined ? renderMeter(item.memory) : '—'}</td>
        <td><span class="nocx-status-badge ${statusClassForItem(item)}">${statusLabelForItem(item)}</span></td>
      </tr>`;
  }

  function airmaxRowReceiver(item) {
    return `
      <tr>
        <td><div class="nocx-host-cell"><strong>${esc(item.name || '—')}</strong><small>${esc(item.host || '—')}</small></div></td>
        <td>${esc(item.model || '—')}</td>
        <td>${item.quality !== null && item.quality !== undefined ? renderMeter(item.quality) : '—'}</td>
        <td>${item.capacity !== null && item.capacity !== undefined ? renderMeter(item.capacity) : '—'}</td>
        <td>${item.cpu !== null && item.cpu !== undefined ? renderMeter(item.cpu) : '—'}</td>
        <td>${item.memory !== null && item.memory !== undefined ? renderMeter(item.memory) : '—'}</td>
        <td><span class="nocx-status-badge ${statusClassForItem(item)}">${statusLabelForItem(item)}</span></td>
      </tr>`;
  }

  function receiverMatchNeedsRefresh(payload) {
    const summary = payload && payload.summary ? payload.summary : null;
    if (!summary) return true;
    const source = String(summary.count_source || '');
    if (source === 'receiver_mac_match') return false;
    if (summary.official_snapshot === true && source !== 'receiver_match_unavailable') return false;
    return source === 'receiver_match_unavailable'
      || summary.source_scan_stale === true
      || summary.snapshot_stale === true
      || summary.total_stations === null
      || summary.total_stations === undefined;
  }

  function renderAirmax() {
    const sectorsPayload = state.sectors || { items: [], summary: { total: 0, down: 0 } };
    const receiversPayload = state.receivers || { items: [], summary: { total: 0, total_stations: 0 } };
    const sectors = Array.isArray(sectorsPayload.items) ? sectorsPayload.items : [];
    const subtowers = Array.isArray(receiversPayload.items) ? receiversPayload.items : [];
    const sectorBody = $('sectorTableBody');
    const subtowerBody = $('subtowerTableBody');
    const receiverStationsBody = $('receiverStationsBody');
    setText('sectorsDownCount', sectorsPayload.summary?.down ?? 0);
    setText('sectorsTotalCount', sectorsPayload.summary?.total ?? sectors.length);
    setText('summarySectorsDown', sectorsPayload.summary?.down ?? 0);
    const receiverCountSource = String(receiversPayload.summary?.count_source || '');
    const receiverCountLabel = String(receiversPayload.summary?.count_label || 'جلسات PPPoE النشطة');
    const totalStations = (receiversPayload.summary?.total_stations ?? null);
    const totalStationsDisplay = (receiversPayload.summary?.total_stations_display ?? null);
    setText('subtowerStationsCount', totalStationsDisplay !== null && totalStationsDisplay !== undefined ? totalStationsDisplay : (totalStations === null || totalStations === undefined ? '—' : totalStations));
    setText('subtowerTotalCount', receiversPayload.summary?.total ?? subtowers.length);
    setText('summaryReceivers', receiversPayload.summary?.total ?? subtowers.length);
    if (sectorBody) sectorBody.innerHTML = sectors.length ? sectors.map(airmaxRowSector).join('') : '<tr><td colspan="5" class="nocx-empty-cell">لا توجد بيانات سكتورات حالياً.</td></tr>';
    if (subtowerBody) subtowerBody.innerHTML = subtowers.length ? subtowers.map(airmaxRowReceiver).join('') : '<tr><td colspan="7" class="nocx-empty-cell">لا توجد بيانات حالياً.</td></tr>';
    if (receiverStationsBody) {
      if (receiverCountSource !== 'receiver_mac_match') {
        const loadingMatch = !!(state.receiverAutoRefresh && state.receiverAutoRefresh.running) || receiverMatchNeedsRefresh(receiversPayload);
        receiverStationsBody.innerHTML = loadingMatch
          ? '<div class="nocx-empty nocx-empty--loading">جاري التحميل</div>'
          : '<div class="nocx-empty">' + esc(receiverCountLabel) + ': <strong>' + esc(totalStations === null || totalStations === undefined ? '—' : totalStations) + '</strong></div>';
      } else {
        receiverStationsBody.innerHTML = subtowers.length ? subtowers.map((it) => `<div class="nocx-receiver-station"><strong>${esc(it.name || '—')}</strong><span>${esc(it.stations_display ?? it.display_clients ?? it.stations ?? it.clients ?? 0)}</span></div>`).join('') : '<div class="nocx-empty">لا توجد بيانات حالياً.</div>';
      }
    }
  }

  const ALERT_DISMISS_MS = 12 * 60 * 60 * 1000;
  const ALERT_DISMISS_TTL_MS = 48 * 60 * 60 * 1000;

  function alertDismissKey(key) {
    return 'nocx_alert_dismiss_' + key;
  }

  function isAlertDismissed(key) {
    try {
      const raw = localStorage.getItem(alertDismissKey(key));
      if (!raw) return false;
      const ts = parseInt(raw, 10);
      if (!Number.isFinite(ts)) return false;
      const age = Date.now() - ts;
      if (age >= ALERT_DISMISS_TTL_MS) {
        localStorage.removeItem(alertDismissKey(key));
        return false;
      }
      return age < ALERT_DISMISS_MS;
    } catch (_) {
      return false;
    }
  }

  function dismissAlert(key) {
    try { localStorage.setItem(alertDismissKey(key), String(Date.now())); } catch (_) {}
    renderAlerts();
  }

  function buildAlertItem({ key, href = '', tone = 'info', count = '', title = '', text = '' }) {
    if (!key || isAlertDismissed(key)) return '';
    const tag = href ? 'a' : 'article';
    const attrs = href
      ? `href="${esc(href)}" class="nocx-alert-line ${esc(tone)}" data-ua-wm-open style="display:flex;text-decoration:none;color:inherit"`
      : `class="nocx-alert-line ${esc(tone)}"`;
    const countHtml = count !== '' ? `<span class="nocx-alert-line__count">${esc(count)}</span>` : '';
    return `<${tag} ${attrs} data-alert-key="${esc(key)}">`
      + `<span class="nocx-alert-line__main">${countHtml}<strong class="nocx-alert-line__title">${esc(title)}</strong><span class="nocx-alert-line__text">${esc(text)}</span></span>`
      + `<button type="button" class="nocx-alert-line__close" data-alert-dismiss="${esc(key)}" aria-label="إغلاق">✕</button>`
      + `</${tag}>`;
  }


  function renderSecurityMonitor() {
    const data = state.securityMonitor || {};
    const s = data.summary || {};
    const worker = s.worker || {};
    const workerFailed = worker.configured === false || worker.ok === false || worker.stale === true;
    const identifying = num(s.identifying, 0);
    const suspect = num(s.suspect, 0);
    const confirmed = num(s.confirmed, 0);
    const adminOpen = num(s.dangerous_logins, s.admin_open);
    const pppoe = num(s.pppoe_conflicts, 0);
    const accountChanges = num(s.account_changes, 0);
    const routerChanges = num(s.router_changes, 0);
    const macConflicts = num(s.mac_conflicts, s.ownership_conflicts);
    const total = num(s.total_attention, identifying + suspect + confirmed);
    setText('securityMonitorIdentifying', identifying);
    setText('securityMonitorPppoe', pppoe);
    setText('securityMonitorAccountChanges', accountChanges);
    setText('securityMonitorRouterChanges', routerChanges);
    setText('securityMonitorMacConflicts', macConflicts);
    setText('securityMonitorDangerousLogins', adminOpen);
    setText('securityMonitorTotal', total);
    setText('securityMonitorLastText', String(workerFailed && worker.message ? worker.message : (s.last_text || 'لا توجد أحداث أمنية حديثة')));
    setText('securityMonitorLastAt', String(workerFailed ? (worker.last_run || '—') : (s.last_seen_at || '—')));
    setText('securityMonitorSubtitle', workerFailed ? 'المراقبة الخلفية تحتاج انتباه' : 'أحداث اليوم فقط');
    const st = String(s.status || (confirmed + adminOpen > 0 ? 'confirmed' : (suspect + pppoe) > 0 ? 'suspect' : identifying > 0 ? 'identifying' : 'clear'));
    const el = $('securityMonitorState');
    if (el) {
      el.classList.remove('is-info','is-warn','is-danger');
      let label = 'مستقر';
      if (workerFailed) { el.classList.add('is-danger'); label = worker.stale ? 'متوقف' : 'فشل الفحص'; }
      else if (st === 'confirmed') { el.classList.add('is-danger'); label = 'مؤكد'; }
      else if (st === 'suspect') { el.classList.add('is-warn'); label = 'اشتباه'; }
      else if (st === 'identifying') { el.classList.add('is-info'); label = 'قيد التعرف'; }
      const span = el.querySelector('span');
      if (span) span.textContent = label;
    }
  }

  function renderAlerts() {
    if (document.querySelector('[data-ua-livebar]')) return;
    const wrap = $('alertFeed');
    if (!wrap) return;
    const alerts = [];
    const counts = state.counts || {};
    const billing = state.billing || { generated_count: 0, pending: { limit: 0, cut: 0 }, cleanup: {} };
    const cleanup = billing.cleanup || {};

    const unsafe = num(counts.unsafe_speed, 0);
    if (unsafe > 0) {
      alerts.push(buildAlertItem({ key: 'unsafe_speed_' + unsafe, tone: 'warn', count: unsafe, title: 'تنبيه تقييد السرعة', text: 'حسابات تعمل بلا تقييد سرعة فعلي.' }));
    }
    const sectorsDown = num(state.sectors?.summary?.down, 0);
    if (sectorsDown > 0) {
      alerts.push(buildAlertItem({ key: 'sectors_down_' + sectorsDown, tone: 'danger', count: sectorsDown, title: 'السكتورات', text: 'سكتورات بحالة DOWN.' }));
    }
    const receiversDown = (Array.isArray(state.receivers?.items) ? state.receivers.items.filter((it) => String(it.state || '').toUpperCase() === 'DOWN' || String(it.state || '').toUpperCase() === 'OFFLINE').length : 0);
    if (receiversDown > 0) {
      alerts.push(buildAlertItem({ key: 'receivers_down_' + receiversDown, tone: 'warn', count: receiversDown, title: 'المستقبلات', text: 'مستقبلات بحالة DOWN/OFFLINE.' }));
    }
    if (Number(billing.generated_count || 0) > 0) {
      alerts.unshift(buildAlertItem({ key: 'generated_' + billing.generated_count + '_' + (billing.cycle?.label || ''), tone: 'info', count: billing.generated_count, title: 'الفواتير', text: 'تم توليد فواتير الدورة الحالية.' }));
    }
    if (Number(billing.pending?.limit || 0) > 0) {
      alerts.unshift(buildAlertItem({ key: 'pending_limit_' + billing.pending.limit, href: 'billing_limit_requests.php', tone: 'warn', count: billing.pending.limit, title: 'طلبات التخفيض', text: 'طلبات جاهزة للمراجعة والموافقة.' }));
    }
    if (Number(billing.pending?.cut || 0) > 0) {
      alerts.unshift(buildAlertItem({ key: 'pending_cut_' + billing.pending.cut, href: 'billing_cut_requests.php', tone: 'danger', count: billing.pending.cut, title: 'طلبات القطع', text: 'طلبات جاهزة للمراجعة والموافقة.' }));
    }
    if (Number(cleanup.warn_total || 0) > 0) {
      alerts.push(buildAlertItem({ key: 'cleanup_warn_' + cleanup.warn_total, tone: 'warn', count: cleanup.warn_total, title: 'تنبيه الحذف', text: 'سيتم حذف الطلبات المعلقة تلقائياً بعد انتهاء مهلة الاحتفاظ.' }));
    }
    if (Number(cleanup.deleted_total || 0) > 0) {
      alerts.push(buildAlertItem({ key: 'cleanup_deleted_' + cleanup.deleted_total, tone: 'info', count: cleanup.deleted_total, title: 'تنظيف الطلبات', text: 'تم حذف طلبات منتهية تلقائياً بعد تجاوز المهلة.' }));
    }

    const netAlerts = Array.isArray(state.networkAlerts?.alerts) ? state.networkAlerts.alerts : [];
    netAlerts.forEach((a) => {
      const sev = String(a.severity || 'warn');
      const tone = sev === 'danger' ? 'danger' : (sev === 'success' ? 'info' : sev);
      const msg = String(a.message || '');
      alerts.unshift(buildAlertItem({
        key: 'mtwatch_' + String(a.id || a.alert_key || msg),
        href: String(a.href || ''),
        tone,
        count: a.hit_count && a.hit_count > 1 ? a.hit_count : '!',
        title: String(a.title || 'تنبيه شبكة'),
        text: msg
      }));
    });

    const secAlerts = Array.isArray(state.securityAlerts?.alerts) ? state.securityAlerts.alerts : [];
    secAlerts.forEach((a) => {
      const sev = String(a.severity || 'warn');
      const tone = sev === 'danger' ? 'danger' : (sev === 'success' ? 'info' : sev);
      const msg = String(a.message || '');
      alerts.unshift(buildAlertItem({
        key: 'security_' + String(a.id || a.alert_key || msg),
        href: String(a.href || 'security_alerts.php'),
        tone,
        count: a.hit_count && a.hit_count > 1 ? a.hit_count : '!',
        title: String(a.title || 'تنبيه أمان'),
        text: msg
      }));
    });

    const html = alerts.filter(Boolean).join('');
    setText('nocAlertCount', alerts.filter(Boolean).length);
    wrap.innerHTML = html || '<div class="nocx-empty">لا يوجد إنذارات حالياً.</div>';
    wrap.querySelectorAll('[data-alert-dismiss]').forEach((btn) => {
      btn.addEventListener('click', (ev) => {
        ev.preventDefault();
        ev.stopPropagation();
        dismissAlert(btn.getAttribute('data-alert-dismiss') || '');
      });
    });
    wrap.querySelectorAll('a[href]').forEach((link) => {
      link.addEventListener('click', () => {
        const details = $('nocAlertDetails');
        if (details) details.open = false;
      });
    });
  }

  function renderSummary() {

    const counts = state.counts || {};
    let pppActive = counts.ppp_active ?? 0;
    if (!pppActive && state.routers?.items?.length) {
      pppActive = state.routers.items.reduce((sum, r) => sum + num(r.ppp_active, 0), 0);
    }
    setText('nocPppActiveTop', pppActive);
    setText('summaryOpenAllMode', num(counts.open_all_mode, 0) === 1 ? 'مفتوحة' : 'طبيعية');
    setText('nocServerTime', new Date().toLocaleTimeString('ar-SY', { hour: '2-digit', minute: '2-digit' }));
  }

  function pushHistory(name, value) { const arr = state.history[name]; if (!arr) return; arr.push(num(value, 0)); while (arr.length > MAX_HISTORY) arr.shift(); }
  function pushSourceHistory(key, value) { if (!state.sourceHistory.has(key)) state.sourceHistory.set(key, []); const arr = state.sourceHistory.get(key); arr.push(num(value, 0)); while (arr.length > MAX_HISTORY) arr.shift(); }

  function getTrafficTarget() {
    const data = state.traffic || { sum: { rx_mbps: 0, tx_mbps: 0, total_mbps: 0 }, iface: { max_mbps: 300 } };
    return {
      rx: num(data.sum?.rx_mbps, 0),
      tx: num(data.sum?.tx_mbps, 0),
      total: num(data.sum?.total_mbps, 0),
      maxMbps: Math.max(1, num(data.iface?.max_mbps, 300))
    };
  }

  function applyTrafficVisual(values, pushPoint = false) {
    const v = values || getTrafficTarget();
    setText('summaryTrafficTotal', fmt1(v.total));
    setText('heroRx', fmt1(v.rx) + ' Mbps');
    setText('heroTx', fmt1(v.tx) + ' Mbps');
    setText('heroTotal', fmt1(v.total) + ' Mbps');
    const maxMbps = Math.max(1, num(v.maxMbps, 300));
    const totalPct = Math.max(0, Math.min(100, (num(v.total, 0) / maxMbps) * 100));
    const bar = $('wanTotalBar');
    if (bar) bar.style.width = `${totalPct}%`;
    if (pushPoint) {
      pushHistory('rx', v.rx);
      pushHistory('tx', v.tx);
      pushHistory('total', v.total);
    }
    drawMainChart();
  }

  function tickTrafficVisual() {
    if (!state.traffic) return;

    const target = getTrafficTarget();
    const visual = state.trafficVisual;

    if (!visual.ready) {
      visual.ready = true;
      visual.rx = target.rx;
      visual.tx = target.tx;
      visual.total = target.total;
      applyTrafficVisual({ ...target }, true);
      return;
    }

    const ease = 0.38;
    const settle = 0.04;
    const nextValue = (current, wanted) => {
      const next = current + (wanted - current) * ease;
      return Math.abs(next - wanted) < settle ? wanted : next;
    };

    visual.rx = nextValue(visual.rx, target.rx);
    visual.tx = nextValue(visual.tx, target.tx);
    visual.total = nextValue(visual.total, target.total);

    applyTrafficVisual({
      rx: visual.rx,
      tx: visual.tx,
      total: visual.total,
      maxMbps: target.maxMbps
    }, true);
  }

  function renderTraffic() {
    const data = state.traffic || { sum: { rx_mbps: 0, tx_mbps: 0, total_mbps: 0 }, kpi: { wan_total_mbps: 0 }, iface: { selected: '', max_mbps: 300 }, sources: [] };

    setText('trafficSourcesLabel', 'المصادر: ' + (data.iface?.selected || '—'));

    if (!state.trafficVisual.ready && state.traffic) {
      const target = getTrafficTarget();
      state.trafficVisual.ready = true;
      state.trafficVisual.rx = target.rx;
      state.trafficVisual.tx = target.tx;
      state.trafficVisual.total = target.total;
      applyTrafficVisual(target, true);
    } else if (state.trafficVisual.ready) {
      applyTrafficVisual({ ...state.trafficVisual, maxMbps: getTrafficTarget().maxMbps }, false);
    }

    const list = $('trafficRailList');
    const sources = Array.isArray(data.sources) ? data.sources : [];
    setText('trafficSourcesCount', sources.length);
    if (list) {
      if (!sources.length) list.innerHTML = '<div class="nocx-empty">لا توجد مصادر ترافيك معرفة.</div>';
      else {
        list.innerHTML = sources.map((src, idx) => {
          const key = `${src.router_id || 0}:${src.iface || idx}`;
          pushSourceHistory(key, src.total_mbps ?? 0);
          const isHold = !!src.stale;
          const badgeClass = src.ok ? 'online' : (isHold ? 'partial' : 'offline');
          const badgeText = src.ok ? 'LIVE' : (isHold ? 'HOLD' : 'DOWN');
          const sourceLabel = esc(src.label || src.iface || 'Source');
          const sourceMeta = esc(src.router_name || '') + (src.iface ? ' · ' + esc(src.iface) : '');
          return `<article class="nocx-traffic-source"><div class="nocx-traffic-source__top"><div class="nocx-traffic-source__name"><strong>${sourceLabel}</strong><small>${sourceMeta}</small></div><span class="nocx-status-badge ${badgeClass}">${badgeText}</span></div><div class="nocx-traffic-source__rates"><div><span>Up</span><strong>${fmt1(src.tx_mbps)} Mbps</strong></div><div><span>Down</span><strong>${fmt1(src.rx_mbps)} Mbps</strong></div><div><span>Total</span><strong>${fmt1(src.total_mbps)} Mbps</strong></div></div></article>`;
          return `<article class="nocx-traffic-source"><div class="nocx-traffic-source__top"><div class="nocx-traffic-source__name"><strong>${esc(src.label || src.iface || 'Source')}</strong><small>${esc(src.router_name || '')}${src.iface ? ' · ' + esc(src.iface) : ''}</small></div><span class="nocx-status-badge ${src.ok ? 'online' : 'offline'}">${src.ok ? 'LIVE' : 'DOWN'}</span></div><div class="nocx-traffic-source__rates"><div><span>Up</span><strong>${fmt1(src.tx_mbps)} Mbps</strong></div><div><span>Down</span><strong>${fmt1(src.rx_mbps)} Mbps</strong></div><div><span>Total</span><strong>${fmt1(src.total_mbps)} Mbps</strong></div></div></article>`;
        }).join('');
      }
    }
  }


  async function refreshReceiverMatch(options = {}) {
    if (ACCESS.receiverRefresh === false) return;
    const btn = $('receiverMatchRefreshBtn');
    const isAuto = !!options.auto;
    if (btn && btn.disabled && !isAuto) return;
    if (state.receiverAutoRefresh && state.receiverAutoRefresh.running && !isAuto) return;
    const old = btn ? btn.innerHTML : '';
    try {
      if (isAuto && state.receiverAutoRefresh) {
        state.receiverAutoRefresh.running = true;
        renderAirmax();
      }
      if (btn) { btn.disabled = true; btn.innerHTML = '<span aria-hidden="true">…</span>'; }
      const out = await fetchJson(URLS.receiverRefresh + '?mode=full');
      if (!out || !out.ok) {
        const failed = Array.isArray(out?.failed_labels) && out.failed_labels.length ? ' — ' + out.failed_labels.slice(0, 4).join('، ') : '';
        throw new Error(((out && (out.message || out.error)) || 'فشل تحديث مطابقة المستقبلات') + failed);
      }
      state.lastFetched.receivers = 0;
      state.lastFetched.sectors = 0;
      state.lastFetched.counts = 0;
      await Promise.allSettled([
        softFetch('receivers', URLS.receivers),
        softFetch('sectors', URLS.sectors),
        softFetch('counts', URLS.counts)
      ]);
      renderAirmax();
      if (!isAuto) showFloatingMessage((out && out.message) ? out.message : 'تم تحديث مطابقة المستقبلات بنجاح.', 'success');
    } catch (e) {
      console.warn('receiver match refresh failed', e);
      if (!isAuto) showFloatingMessage((e && e.message) ? e.message : 'فشل تحديث مطابقة المستقبلات، وبقيت النتيجة الرسمية كما هي.', 'error');
    } finally {
      if (isAuto && state.receiverAutoRefresh) {
        state.receiverAutoRefresh.running = false;
        renderAirmax();
      }
      if (btn) { btn.disabled = false; btn.innerHTML = old || '<span aria-hidden="true">↻</span>'; }
    }
  }

  async function autoRefreshReceiverMatchOnOpen() {
    if (ACCESS.receiverRefresh === false) return;
    if (!state.receiverAutoRefresh || state.receiverAutoRefresh.attempted || state.receiverAutoRefresh.running) return;
    state.receiverAutoRefresh.attempted = true;
    if (!state.receivers || !state.receivers.summary) {
      try {
        state.inFlight.receivers = false;
        state.lastFetched.receivers = 0;
        await softFetch('receivers', URLS.receivers);
      } catch (_) {}
    }
    if (!receiverMatchNeedsRefresh(state.receivers)) return;
    await refreshReceiverMatch({ auto: true });
  }

  const receiverMatchRefreshBtn = $('receiverMatchRefreshBtn');
  if (receiverMatchRefreshBtn) receiverMatchRefreshBtn.addEventListener('click', () => { void refreshReceiverMatch(); });

  let messageQueuePolling = false;
  function renderMessageQueue(run, active) {
    const strip = $('dashboardMessageProgress');
    if (!strip) return;
    if (!active || !run || !['TESTING','WAITING_CONFIRMATION','RUNNING','TEST_FAILED'].includes(String(run.status || ''))) {
      strip.hidden = true;
      strip.classList.remove('is-waiting');
      return;
    }
    strip.hidden = false;
    strip.classList.toggle('is-waiting', run.status === 'WAITING_CONFIRMATION' || run.status === 'TEST_FAILED');
    const labels = {
      TESTING: 'جارٍ إرسال رسالة الاختبار',
      WAITING_CONFIRMATION: 'الطابور بانتظار موافقتك',
      RUNNING: 'جارٍ إرسال الرسائل',
      TEST_FAILED: 'فشل إرسال رسالة الاختبار'
    };
    setText('dashboardMessageProgressLabel', labels[run.status] || 'طابور الرسائل');
    setText('dashboardMessageProgressMeta', `${run.total || 0} رسالة في الطابور`);
    setText('dashboardMessageSent', run.sent || 0);
    setText('dashboardMessageFailed', run.failed || 0);
    setText('dashboardMessagePercent', `${run.percent || 0}%`);
    const bar = $('dashboardMessageProgressBar');
    if (bar) bar.style.width = `${Math.max(0, Math.min(100, num(run.percent, 0)))}%`;
  }

  async function pollMessageQueue() {
    if (messageQueuePolling || !canPollDashboard()) return;
    messageQueuePolling = true;
    try {
      const status = await fetchJson(URLS.messageQueueStatus);
      renderMessageQueue(status?.run || null, !!status?.active);
      if (status?.active && status?.run?.status === 'RUNNING') {
        const body = new URLSearchParams({
          action:'step',
          run_id:String(status.run.id),
          limit:'2',
          ua_csrf:String(window.NOC_CSRF || '')
        });
        const response = await fetch(URLS.messageQueueRun, { method:'POST', body, credentials:'same-origin', cache:'no-store' });
        const next = await response.json();
        if (next?.run) renderMessageQueue(next.run, next.run.status === 'RUNNING');
      }
    } catch (_) {
    } finally {
      messageQueuePolling = false;
    }
  }

  function renderAll() { renderSummary(); renderRouters(); renderAirmax(); renderTraffic(); renderSecurityMonitor(); renderAlerts(); }

  async function pollCore() {
    if (!canPollDashboard()) return;
    await Promise.allSettled([
      softFetch('counts', URLS.counts),
      softFetch('routers', URLS.routers),
      softFetch('traffic', URLS.traffic)
    ]);
  }

  async function pollSecondary() {
    if (!canPollDashboard()) return;
    await Promise.allSettled([
      softFetch('sectors', URLS.sectors),
      softFetch('receivers', URLS.receivers),
      softFetch('billing', URLS.billingAlerts),
      softFetch('networkAlerts', URLS.networkAlerts),
      softFetch('securityAlerts', URLS.securityAlerts),
      softFetch('securityMonitor', URLS.securityMonitor)
    ]);
  }

  setInterval(() => { void pollCore(); }, UI_REFRESH_MS);
  setInterval(() => { void pollSecondary(); }, SECONDARY_REFRESH_MS);
  setInterval(() => { void pollMessageQueue(); }, 1400);
  setInterval(() => { tickTrafficVisual(); }, TRAFFIC_UI_MS);
  window.addEventListener('resize', () => { drawMainChart(); });
  setTimeout(() => { void pollCore(); }, 350);
  setTimeout(() => { void pollSecondary(); }, 2200);
  setTimeout(() => { void autoRefreshReceiverMatchOnOpen(); }, 3200);
  setTimeout(() => { void pollMessageQueue(); }, 700);
})();
