'use strict';

// ── Menú desplegable ──────────────────────────────────────────────────────────
document.querySelectorAll('.nav-item').forEach(item => {
  item.addEventListener('click', (e) => {
    e.stopPropagation();
    const isOpen = item.classList.contains('open');
    document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('open'));
    if (!isOpen) item.classList.add('open');
  });
});
document.addEventListener('click', () => {
  document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('open'));
});

// ── Acciones del menú ─────────────────────────────────────────────────────────
document.querySelectorAll('.dd-item').forEach(item => {
  item.addEventListener('click', (e) => {
    e.stopPropagation();
    document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('open'));
    const action = item.dataset.action;

    switch (action) {
      case 'users-list':
      case 'users-create':
        if (!window.Auth?.isSuperAdmin()) {
          alert('Superadmin access required for user management.');
          return;
        }
        openUsersModal(action === 'users-create');
        break;
      case 'charts-catalog':
        openChartsModal('tab-installed');
        break;
      case 'charts-install':
        openChartsModal('tab-upload');
        break;
      case 'charts-noaa':
        openChartsModal('tab-catalog');
        break;
      case 'settings-datasource':
        openSettingsModal('ais');
        break;
      case 'settings-station':
        openSettingsModal('station');
        break;
      case 'settings-alerts':
        openSettingsModal('alerts');
        break;
      case 'settings-equipment':
        openSettingsModal('equipment');
        break;
      case 'lamps-catalog':
        openLampsModal();
        break;
      case 'contacts-catalog':
        openContactsModal();
        break;
      case 'recordings-list':
        openRecordingsModal();
        break;
      case 'ais-history':
        if (window.openTrackHistoryModal) openTrackHistoryModal();
        else openRecordingsModal();
        break;
      case 'org-management':
        if (window.Auth?.isSuperAdmin() || window.Auth?.isAdmin()) {
          if (window.openOrgModal) openOrgModal();
        } else {
          alert('Admin access required.');
        }
        break;
      case 'export-data':
        alert('CSV export coming soon.');
        break;
    }
  });
});

// ── MODAL USUARIOS ────────────────────────────────────────────────────────────
async function openUsersModal(openCreate = false) {
  _showModal('modal-users');
  await loadUsersList();
  if (openCreate) openUserForm();
}

async function loadUsersList() {
  const body = document.getElementById('modal-users-body');
  body.innerHTML = '<div style="color:var(--text-muted);padding:8px">Loading...</div>';
  try {
    const res  = await fetch(`${API}/auth/users`, {
      headers: { Authorization: `Bearer ${window.Auth.token()}` }
    });
    if (res.status === 401) { body.innerHTML = '<div style="color:var(--red);padding:8px">Session expired — please log in again (F5).</div>'; return; }
    const data  = await res.json();
    const users = Array.isArray(data) ? data : [];
    // Also fetch companies to resolve company_id → name
    let companyMap = {};
    try {
      const cr = await fetch(`${API}/org/companies`,
        { headers: { Authorization: `Bearer ${window.Auth.token()}` } });
      if (cr.ok) {
        const cs = await cr.json();
        cs.forEach(c => { companyMap[c.id] = c.name; });
        window._orgPortsCache = window._orgPortsCache || [];   // used by company hint
      }
    } catch {}

    body.innerHTML = `
      <table class="users-table">
        <thead>
          <tr>
            <th>USERNAME</th><th>FULL NAME</th><th>EMAIL</th>
            <th>ROLE</th><th>COMPANY</th><th>STATUS</th><th></th>
          </tr>
        </thead>
        <tbody>
          ${users.map(u => `
            <tr data-username="${u.username}">
              <td class="mono">${u.username}</td>
              <td>${u.nombre}</td>
              <td class="mono" style="font-size:0.72rem">${u.email || '--'}</td>
              <td><span class="session-role ${roleClass(u.role)}">${u.role}</span></td>
              <td style="font-size:0.72rem;color:var(--text-muted)">${u.company_id ? (companyMap[u.company_id] || u.company_id) : '--'}</td>
              <td><span style="color:${u.activo ? 'var(--green)' : 'var(--red)'}">${u.activo ? 'ACTIVE' : 'INACTIVE'}</span></td>
              <td style="display:flex;gap:4px;justify-content:flex-end">
                <button class="chart-row-btn" onclick="editUser('${u.username}','${u.nombre}','${u.email||''}','${u.role}','${u.company_id||''}',${u.activo})">EDIT</button>
                ${u.username !== 'admin' ? `<button class="chart-row-btn danger" onclick="deleteUser('${u.username}')">DELETE</button>` : ''}
              </td>
            </tr>`).join('')}
        </tbody>
      </table>
    `;
  } catch (e) {
    body.innerHTML = `<div class="modal-error" style="display:block">Error loading users: ${e.message}</div>`;
  }
}

function roleClass(role) {
  return {
    SUPERADMIN:   'role-superadmin',
    ADMIN:        'role-admin',
    CLIENT_ADMIN: 'role-admin',   // same cyan badge as admin but label differs
    USER:         'role-user',
  }[role] || 'role-user';
}

// ── MODAL CREAR USUARIO ───────────────────────────────────────────────────────
let _ufCompanies = [];   // cached list from /org/companies

async function _loadCompaniesForForm() {
  try {
    const r = await fetch(`${API}/org/companies`,
      { headers: { Authorization: `Bearer ${window.Auth?.token()}` } });
    _ufCompanies = await r.json();
  } catch { _ufCompanies = []; }

  const sel = document.getElementById('uf-company');
  sel.innerHTML = '<option value="">— Select company —</option>' +
    _ufCompanies.map(c => `<option value="${c.id}">${c.name}</option>`).join('');
}

function _ufToggleCompanyRow() {
  const role = document.getElementById('uf-role').value;
  const row  = document.getElementById('uf-company-row');
  // Company field required for client roles (USER and CLIENT_ADMIN)
  row.style.display = (role === 'USER' || role === 'CLIENT_ADMIN') ? '' : 'none';
}

document.getElementById('uf-role').addEventListener('change', _ufToggleCompanyRow);

// Auto-suggest username & full-name when a company is selected
document.getElementById('uf-company').addEventListener('change', () => {
  const cid     = document.getElementById('uf-company').value;
  const company = _ufCompanies.find(c => c.id === cid);
  const hint    = document.getElementById('uf-company-hint');
  if (!company) { hint.textContent = ''; return; }

  // Port name for the hint
  const portName = company.port_id
    ? (window._orgPortsCache?.find?.(p => p.id === company.port_id)?.name || company.port_id)
    : '';
  hint.textContent = portName ? `Home port: ${portName}` : '';

  // Auto-fill username: slug from company name (lowercase, no spaces)
  const slug = company.name.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
  const usernameEl = document.getElementById('uf-username');
  if (!usernameEl.readOnly && !usernameEl.value) {
    usernameEl.value = slug;
  }
  // Auto-fill full name if empty
  const nombreEl = document.getElementById('uf-nombre');
  if (!nombreEl.value) {
    nombreEl.value = company.name + (portName ? ` — ${portName}` : '');
  }
});

async function openUserForm() {
  document.getElementById('user-form-title').textContent = 'CREATE USER';
  document.getElementById('uf-username').value    = '';
  document.getElementById('uf-username').readOnly = false;
  document.getElementById('uf-nombre').value      = '';
  document.getElementById('uf-email').value       = '';
  document.getElementById('uf-password').value    = '';
  document.getElementById('uf-role').value        = 'USER';
  document.getElementById('uf-company').value     = '';
  document.getElementById('uf-company-hint').textContent = '';
  document.getElementById('uf-error').classList.add('hidden');
  document.getElementById('btn-uf-save').dataset.editMode = '';
  _ufToggleCompanyRow();
  await _loadCompaniesForForm();
  _showModal('modal-user-form');
}

document.getElementById('btn-user-new').addEventListener('click', openUserForm);

window.editUser = function(username, nombre, email, role, company_id, activo) {
  document.getElementById('user-form-title').textContent = `EDIT — ${username}`;
  document.getElementById('uf-username').value    = username;
  document.getElementById('uf-username').readOnly = true;
  document.getElementById('uf-nombre').value      = nombre;
  document.getElementById('uf-email').value       = email;
  document.getElementById('uf-password').value    = '';
  document.getElementById('uf-role').value        = role;
  document.getElementById('uf-company').value     = company_id || '';
  document.getElementById('uf-company-hint').textContent = '';
  document.getElementById('uf-error').classList.add('hidden');
  document.getElementById('btn-uf-save').dataset.editMode = '1';
  _ufToggleCompanyRow();
  _loadCompaniesForForm().then(() => {
    document.getElementById('uf-company').value = company_id || '';
  });
  _showModal('modal-user-form');
};

window.deleteUser = async function(username) {
  if (!confirm(`Delete user "${username}"? This cannot be undone.`)) return;
  try {
    const res = await fetch(`${API}/auth/users/${username}`, {
      method: 'DELETE',
      headers: { Authorization: `Bearer ${window.Auth.token()}` },
    });
    if (!res.ok) { const e = await res.json(); throw new Error(e.detail); }
    await loadUsersList();
  } catch (e) { alert('Error: ' + e.message); }
};

document.getElementById('btn-uf-save').addEventListener('click', async () => {
  const username   = document.getElementById('uf-username').value.trim();
  const nombre     = document.getElementById('uf-nombre').value.trim();
  const email      = document.getElementById('uf-email').value.trim();
  const password   = document.getElementById('uf-password').value;
  const role       = document.getElementById('uf-role').value;
  const company_id = document.getElementById('uf-company').value || null;
  const errEl      = document.getElementById('uf-error');

  const isEdit = document.getElementById('btn-uf-save').dataset.editMode === '1';

  // Company required for USER role
  if (role === 'USER' && !company_id) {
    errEl.textContent = 'Select a company for USER accounts.';
    errEl.classList.remove('hidden');
    return;
  }
  if (!isEdit && (!username || !nombre || !password)) {
    errEl.textContent = 'Username, full name and password are required.';
    errEl.classList.remove('hidden');
    return;
  }
  if (isEdit && !nombre) {
    errEl.textContent = 'Full name is required.';
    errEl.classList.remove('hidden');
    return;
  }

  try {
    let res;
    if (isEdit) {
      const body = { nombre, email: email || null, role, company_id };
      if (password) body.password = password;
      res = await fetch(`${API}/auth/users/${username}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${window.Auth.token()}` },
        body: JSON.stringify(body),
      });
    } else {
      res = await fetch(`${API}/auth/users`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${window.Auth.token()}` },
        body: JSON.stringify({ username, nombre, email: email || null, password, role, company_id }),
      });
    }
    if (!res.ok) {
      const err = await res.json();
      throw new Error(err.detail || 'Error saving user');
    }
    document.getElementById('uf-username').readOnly = false;
    document.getElementById('btn-uf-save').dataset.editMode = '';
    _hideModal('modal-user-form');
    _showModal('modal-users');
    await loadUsersList();
  } catch (e) {
    errEl.textContent = e.message;
    errEl.classList.remove('hidden');
  }
});

// ── MODAL CARTAS ──────────────────────────────────────────────────────────────
function openChartsModal(tab = 'tab-catalog') {
  document.getElementById('chart-install-status').innerHTML = '';
  document.getElementById('chart-save-status').textContent  = '';
  document.getElementById('chart-file').value = '';
  _showModal('modal-charts');
  _activateChartTab(tab);
  if (tab === 'tab-catalog' || tab === 'tab-installed') loadNOAACatalog();
  if (tab === 'tab-installed') loadInstalledCells();
}

// Chart tabs
document.querySelectorAll('.stab[data-ctab]').forEach(btn => {
  btn.addEventListener('click', () => {
    const t = btn.dataset.ctab;
    _activateChartTab(t);
    if (t === 'tab-installed') loadInstalledCells();
    if (t === 'tab-catalog')   loadNOAACatalog();
  });
});

function _activateChartTab(id) {
  document.querySelectorAll('.ctab-panel').forEach(p => p.classList.add('hidden'));
  document.querySelectorAll('.stab[data-ctab]').forEach(b => b.classList.remove('active'));
  document.getElementById(id)?.classList.remove('hidden');
  document.querySelector(`.stab[data-ctab="${id}"]`)?.classList.add('active');
}

async function loadNOAACatalog() {
  const tbody = document.getElementById('noaa-catalog-body');
  tbody.innerHTML = '<tr><td colspan="4" style="color:var(--text-muted)">Loading…</td></tr>';
  try {
    const r = await fetch(`${API}/charts/noaa-catalog`);
    const cells = await r.json();
    tbody.innerHTML = cells.map(c => `
      <tr id="crow-${c.id}">
        <td style="font-family:monospace;color:var(--accent)">${c.id}</td>
        <td>${c.description}<br><span style="font-size:0.68rem;color:var(--text-muted)">1:${c.scale.toLocaleString()}</span></td>
        <td>${c.installed
          ? '<span style="color:#4caf50">INSTALLED</span>'
          : '<span style="color:var(--text-muted)">—</span>'}</td>
        <td>
          ${c.installed
            ? `<button class="chart-row-btn danger" onclick="deleteCell('${c.id}')">REMOVE</button>`
            : `<button class="chart-row-btn" onclick="downloadNOAA('${c.id}')">DOWNLOAD</button>`}
        </td>
      </tr>`).join('');
  } catch(e) {
    tbody.innerHTML = '<tr><td colspan="4" style="color:var(--red)">Error loading catalog</td></tr>';
  }
}

async function loadInstalledCells() {
  const tbody = document.getElementById('installed-body');
  const empty = document.getElementById('installed-empty');
  empty.style.display = 'none';
  tbody.innerHTML = '<tr><td colspan="4" style="color:var(--text-muted)">Loading…</td></tr>';
  try {
    const r = await fetch(`${API}/charts/cells`);
    if (!r.ok) throw new Error(`Server error ${r.status}`);
    const cells = await r.json();
    if (!Array.isArray(cells) || !cells.length) {
      tbody.innerHTML = '';
      empty.style.display = '';
      return;
    }
    empty.style.display = 'none';
    tbody.innerHTML = cells.map(c => `
      <tr id="crow-${c.id}">
        <td style="font-family:monospace;color:var(--accent)">${c.id}</td>
        <td id="feat-${c.id}">${c.features} features</td>
        <td>
          <select class="form-input-select" style="padding:2px 6px;font-size:0.7rem"
                  onchange="setCellRegion('${c.id}', this.value)">
            <option value="A" ${c.region === 'A' ? 'selected' : ''}>A — Europe/Asia (Red=Port)</option>
            <option value="B" ${c.region === 'B' ? 'selected' : ''}>B — Americas/Japan (Green=Port)</option>
          </select>
        </td>
        <td style="white-space:nowrap;display:flex;gap:4px">
          <button class="chart-row-btn" id="rbtn-${c.id}" onclick="rebuildCell('${c.id}')">REBUILD</button>
          <button class="chart-row-btn danger" onclick="deleteCell('${c.id}')">REMOVE</button>
        </td>
      </tr>`).join('');
  } catch(e) {
    tbody.innerHTML = `<tr><td colspan="4" style="color:var(--red)">Error: ${e.message}</td></tr>`;
  }
}

// ── Rebuild a single chart cell (re-parses .000 → regenerates GeoJSON cache)
window.rebuildCell = async function(cellId) {
  const btn    = document.getElementById(`rbtn-${cellId}`);
  const featEl = document.getElementById(`feat-${cellId}`);
  const status = document.getElementById('chart-install-status');
  if (btn) { btn.textContent = 'REBUILDING…'; btn.disabled = true; }
  status.innerHTML = `<span style="color:var(--cyan)">Rebuilding ${cellId}…</span>`;
  try {
    const r = await fetch(`${API}/charts/cells/${cellId}/rebuild`, { method: 'POST' });
    if (!r.ok) throw new Error((await r.json()).detail || `Error ${r.status}`);
    status.innerHTML = `<span style="color:#4caf50">✓ ${cellId} rebuilt</span>`;
    // Refresh feature count
    const cells = await (await fetch(`${API}/charts/cells`)).json();
    const updated = cells.find(c => c.id === cellId);
    if (updated && featEl) featEl.textContent = `${updated.features} features`;
    // Reload map layers
    window.reloadAllChartLayers?.();
  } catch(e) {
    status.innerHTML = `<span style="color:var(--red)">✗ ${e.message}</span>`;
  } finally {
    if (btn) { btn.textContent = 'REBUILD'; btn.disabled = false; }
  }
};

// ── Rebuild ALL chart cells
window.rebuildAllCells = async function() {
  const btn    = document.getElementById('btn-rebuild-all');
  const status = document.getElementById('chart-install-status');
  if (btn) { btn.textContent = 'REBUILDING ALL…'; btn.disabled = true; }
  status.innerHTML = `<span style="color:var(--cyan)">Rebuilding all charts… (may take a moment)</span>`;
  try {
    const r = await fetch(`${API}/charts/rebuild-cache`, { method: 'POST' });
    const data = await r.json();
    if (!r.ok) throw new Error(data.detail || `Error ${r.status}`);
    const n = data.rebuilt?.length || 0;
    status.innerHTML = `<span style="color:#4caf50">✓ ${n} chart(s) rebuilt: ${data.rebuilt?.join(', ')}</span>`;
    loadInstalledCells();           // refresh table
    window.reloadAllChartLayers?.();
  } catch(e) {
    status.innerHTML = `<span style="color:var(--red)">✗ ${e.message}</span>`;
  } finally {
    if (btn) { btn.textContent = 'REBUILD ALL'; btn.disabled = false; }
  }
};

window.setCellRegion = async function(cellId, region) {
  try {
    const r = await fetch(`${API}/charts/cells/${cellId}/region?region=${region}`, { method: 'PATCH' });
    if (!r.ok) throw new Error((await r.json()).detail || 'Failed');
    window.reloadAllChartLayers?.();   // re-render with new region colours
  } catch(e) {
    alert('Could not set region: ' + e.message);
  }
};

window.downloadNOAA = async function(cellId) {
  const btn = document.querySelector(`#crow-${cellId} .chart-row-btn`);
  if (btn) { btn.textContent = 'DOWNLOADING…'; btn.disabled = true; }
  const status = document.getElementById('chart-install-status');
  status.innerHTML = `<span style="color:var(--cyan)">Downloading ${cellId} from NOAA…</span>`;
  try {
    const r = await fetch(`${API}/charts/download-noaa/${cellId}`, { method: 'POST' });
    const data = await r.json();
    if (!r.ok) throw new Error(data.detail || 'Download failed');
    status.innerHTML = `<span style="color:#4caf50">✓ ${cellId} installed — ${data.installed?.join(', ')}</span>`;
    loadNOAACatalog();
    window.reloadAllChartLayers?.();
  } catch(e) {
    status.innerHTML = `<span style="color:var(--red)">✗ ${e.message}</span>`;
    if (btn) { btn.textContent = 'DOWNLOAD'; btn.disabled = false; }
  }
};

window.deleteCell = async function(cellId) {
  if (!confirm(`Remove chart cell ${cellId}?`)) return;
  const status = document.getElementById('chart-install-status');
  await fetch(`${API}/charts/cells/${cellId}`, { method: 'DELETE' });
  status.innerHTML = `<span style="color:var(--text-muted)">${cellId} removed</span>`;
  loadNOAACatalog();
  loadInstalledCells();
  window.reloadAllChartLayers?.();
};

document.getElementById('btn-chart-upload')?.addEventListener('click', async () => {
  const f = document.getElementById('chart-file').files[0];
  const status = document.getElementById('chart-upload-status');
  if (!f) { status.innerHTML = '<span style="color:var(--red)">Select a file first</span>'; return; }
  status.innerHTML = `<span style="color:var(--cyan)">Uploading ${f.name}…</span>`;
  const fd = new FormData();
  fd.append('file', f);
  try {
    const r = await fetch(`${API}/charts/upload`, { method: 'POST', body: fd });
    const data = await r.json();
    if (!r.ok) throw new Error(data.detail);
    status.innerHTML = `<span style="color:#4caf50">✓ Installed: ${data.installed.join(', ')}</span>`;
    loadNOAACatalog();
    window.reloadAllChartLayers?.();
  } catch(e) {
    status.innerHTML = `<span style="color:var(--red)">✗ ${e.message}</span>`;
  }
});

// ── Cerrar modales ────────────────────────────────────────────────────────────
[
  ['btn-close-users',    'modal-users'],
  ['btn-close-users2',   'modal-users'],
  ['btn-close-charts',   'modal-charts'],
  ['btn-close-charts2',  'modal-charts'],
  ['btn-close-user-form','modal-user-form'],
  ['btn-uf-cancel',      'modal-user-form'],
].forEach(([btnId, modalId]) => {
  document.getElementById(btnId)?.addEventListener('click', () => _hideModal(modalId));
});

// ── Rebuild ENC symbol cache (footer button — delegates to rebuildAllCells) ───
// onclick="rebuildAllCells()" is already set in HTML; this listener is a no-op
// kept only so existing code that wires the button doesn't break.
document.getElementById('btn-rebuild-cache')?.addEventListener('click', () => {});

// ── Helpers modales ───────────────────────────────────────────────────────────
function _showModal(id) {
  document.getElementById('modal-overlay').classList.remove('hidden');
  document.getElementById(id).classList.remove('hidden');
}
function _hideModal(id) {
  document.getElementById(id).classList.add('hidden');
  const anyOpen = [...document.querySelectorAll('.modal')].some(m => !m.classList.contains('hidden'));
  if (!anyOpen) document.getElementById('modal-overlay').classList.add('hidden');
}

window._showModal = _showModal;
window._hideModal = _hideModal;

// ── MODAL SETTINGS ────────────────────────────────────────────────────────
const SETTINGS_KEY = 'ams_settings';

function loadSettings() {
  try { return JSON.parse(localStorage.getItem(SETTINGS_KEY)) || {}; } catch { return {}; }
}
function saveSettings(data) {
  localStorage.setItem(SETTINGS_KEY, JSON.stringify(data));
}

async function openSettingsModal(tab = 'ais') {
  document.getElementById('settings-status').textContent = 'Loading…';
  let s = {};
  try {
    const r = await fetch(`${API}/settings`, {
      headers: { Authorization: `Bearer ${window.Auth?.token()}` },
    });
    if (r.ok) {
      s = await r.json();
      saveSettings(s);  // keep localStorage in sync as cache
    } else {
      s = loadSettings();  // fallback to localStorage if not authenticated yet
    }
  } catch {
    s = loadSettings();
  }

  document.getElementById('set-ais-source').value    = s.ais_source      || s.aisSource    || 'SIMULATOR';
  document.getElementById('set-serial-port').value   = s.ais_serial_port || s.serialPort   || '';
  document.getElementById('set-baud').value          = s.ais_baud        || s.baud         || '38400';
  document.getElementById('set-net-addr').value      = s.ais_net_addr    || s.netAddr      || '';
  document.getElementById('set-station-name').value  = s.station_name    || s.stationName  || '';
  document.getElementById('set-iala-region').value   = s.iala_region     || s.ialaRegion   || window.IALA_REGION || 'B';
  document.getElementById('set-gps-port').value      = s.gps_port        || s.gpsPort      || '';
  document.getElementById('set-ant-lat').value       = s.antenna_lat     ?? s.antLat       ?? '';
  document.getElementById('set-ant-lon').value       = s.antenna_lon     ?? s.antLon       ?? '';
  document.getElementById('set-ant-height').value    = s.antenna_height_m ?? s.antHeight   ?? '';
  document.getElementById('set-station-notes').value = s.station_notes   || s.stationNotes || '';
  document.getElementById('set-disp-warn').value     = s.displacement_warn_m  ?? s.dispWarn  ?? 10;
  document.getElementById('set-disp-alarm').value    = s.displacement_alarm_m ?? s.dispAlarm ?? 15;
  document.getElementById('set-prox-warn').value     = s.proximity_alert_meters ?? s.proxWarn ?? 300;
  document.getElementById('set-rec-trigger').value   = s.auto_record_trigger_m  ?? s.recTrigger ?? 200;
  document.getElementById('set-batt-warn').value     = s.battery_warn_v  ?? s.battWarn  ?? 11.5;
  document.getElementById('set-batt-alarm').value    = s.battery_alarm_v ?? s.battAlarm ?? 10.8;
  document.getElementById('set-smtp-host').value      = s.smtp_host      || '';
  document.getElementById('set-smtp-port').value      = s.smtp_port      || 587;
  document.getElementById('set-smtp-user').value      = s.smtp_user      || '';
  document.getElementById('set-smtp-password').value  = s.smtp_password  || '';
  document.getElementById('set-smtp-from').value      = s.smtp_from      || '';
  document.getElementById('set-smtp-from-name').value = s.smtp_from_name || '';
  document.getElementById('set-smtp-tls').checked     = s.smtp_use_tls !== false;
  document.getElementById('settings-status').textContent = '';

  _activateTab(tab);
  _showModal('modal-settings');
}

function _activateTab(tab) {
  document.querySelectorAll('.stab').forEach(b => {
    b.classList.toggle('active', b.dataset.tab === tab);
  });
  document.querySelectorAll('.stab-panel').forEach(p => {
    p.classList.toggle('hidden', p.id !== `stab-${tab}`);
  });
}

document.querySelectorAll('.stab[data-tab]').forEach(btn => {
  btn.addEventListener('click', () => _activateTab(btn.dataset.tab));
});

// ── EQUIPMENT SCANNER ─────────────────────────────────────────────────────────
const DEVICE_ICONS = {
  GPS: '🛰', AIS: '📡', ATON: '🔆', GYRO: '🧭',
  RADAR: '📻', SONAR: '🌊', WIND: '💨', AUTOPILOT: '🎛',
  'IN USE': '🔒', 'NO DATA': '—', UNKNOWN: '❓',
};
const DEVICE_COLOR = {
  GPS:'var(--green)', AIS:'var(--cyan)', ATON:'var(--yellow)',
  GYRO:'var(--cyan)', RADAR:'#f97316', SONAR:'#06b6d4',
  WIND:'#a78bfa', AUTOPILOT:'#f472b6',
  'IN USE':'var(--text-muted)', 'NO DATA':'var(--text-muted)', UNKNOWN:'var(--yellow)',
};

document.getElementById('btn-scan-ports')?.addEventListener('click', async () => {
  const btn    = document.getElementById('btn-scan-ports');
  const status = document.getElementById('scan-status');
  const list   = document.getElementById('equipment-list');
  btn.disabled = true;
  btn.textContent = 'SCANNING…';
  status.textContent = 'Reading serial ports — this may take 15–30 s…';
  list.innerHTML = '';
  try {
    const r    = await fetch(`${API}/equipment/scan`);
    const devs = await r.json();
    if (!devs.length) {
      list.innerHTML = '<div style="color:var(--text-muted);font-size:0.78rem">No serial ports detected.</div>';
    } else {
      list.innerHTML = devs.map(d => {
        const icon  = DEVICE_ICONS[d.device_type] || '?';
        const color = DEVICE_COLOR[d.device_type]  || 'var(--text-primary)';
        const sentences = (d.sentences || []).map(s =>
          `<div class="eq-sentence">${s}</div>`).join('');
        return `
        <div class="eq-card">
          <div class="eq-card-header">
            <span class="eq-type" style="color:${color}">${icon} ${d.device_type}</span>
            <span class="eq-port">${d.port}${d.baud ? ' @ ' + d.baud + ' baud' : ''}</span>
          </div>
          <div class="eq-desc">${d.description}</div>
          <div class="eq-hwid">${d.port_desc || ''}</div>
          ${sentences ? `<div class="eq-sentences">${sentences}</div>` : ''}
        </div>`;
      }).join('');
    }
    status.textContent = `${devs.length} port(s) scanned.`;
  } catch(e) {
    status.textContent = 'Scan failed: ' + e.message;
  } finally {
    btn.disabled = false;
    btn.textContent = 'SCAN PORTS';
  }
});

document.getElementById('btn-settings-save')?.addEventListener('click', async () => {
  const s = {
    aisSource:    document.getElementById('set-ais-source').value,
    serialPort:   document.getElementById('set-serial-port').value.trim(),
    baud:         document.getElementById('set-baud').value,
    netAddr:      document.getElementById('set-net-addr').value.trim(),
    ialaRegion:   document.getElementById('set-iala-region').value,
    gpsPort:      document.getElementById('set-gps-port').value.trim(),
    stationName:  document.getElementById('set-station-name').value.trim(),
    antLat:       parseFloat(document.getElementById('set-ant-lat').value) || null,
    antLon:       parseFloat(document.getElementById('set-ant-lon').value) || null,
    antHeight:    parseFloat(document.getElementById('set-ant-height').value) || null,
    stationNotes: document.getElementById('set-station-notes').value.trim(),
    dispWarn:     parseFloat(document.getElementById('set-disp-warn').value),
    dispAlarm:    parseFloat(document.getElementById('set-disp-alarm').value),
    proxWarn:     parseFloat(document.getElementById('set-prox-warn').value),
    recTrigger:   parseFloat(document.getElementById('set-rec-trigger').value),
    battWarn:     parseFloat(document.getElementById('set-batt-warn').value),
    battAlarm:    parseFloat(document.getElementById('set-batt-alarm').value),
  };
  saveSettings(s);
  updateStatusBar();
  // Apply IALA region live for non-S57 aids (S-57 cells have their own per-cell region)
  if (window.IALA_REGION !== s.ialaRegion) {
    window.IALA_REGION = s.ialaRegion;
    localStorage.setItem('ams_iala', s.ialaRegion);
    if (window._iconCache) Object.keys(window._iconCache).forEach(k => { if (k.startsWith('boya_lat')) delete window._iconCache[k]; });
    if (window.aidsSource) window.aidsSource.changed();
  }
  // Push to backend so alert thresholds, GPS, AIS source, etc. apply live
  const st = document.getElementById('settings-status');
  st.textContent = 'Saving…';
  st.className = 'save-status';
  try {
    const res = await fetch(`${API}/settings`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json',
                 'Authorization': `Bearer ${window.Auth?.token()}` },
      body: JSON.stringify({
        ais_source:               s.aisSource,
        ais_serial_port:          s.serialPort,
        ais_baud:                 parseInt(s.baud) || 38400,
        ais_net_addr:             s.netAddr,
        gps_port:                 s.gpsPort,
        gps_baud:                 9600,
        station_name:             s.stationName,
        iala_region:              s.ialaRegion,
        antenna_lat:              s.antLat,
        antenna_lon:              s.antLon,
        antenna_height_m:         s.antHeight,
        station_notes:            s.stationNotes,
        displacement_warn_m:      s.dispWarn,
        displacement_alarm_m:     s.dispAlarm,
        proximity_alert_meters:   s.proxWarn,
        auto_record_trigger_m:    s.recTrigger,
        battery_warn_v:           s.battWarn,
        battery_alarm_v:          s.battAlarm,
        smtp_host:                document.getElementById('set-smtp-host').value.trim(),
        smtp_port:                parseInt(document.getElementById('set-smtp-port').value) || 587,
        smtp_user:                document.getElementById('set-smtp-user').value.trim(),
        smtp_password:            document.getElementById('set-smtp-password').value,
        smtp_from:                document.getElementById('set-smtp-from').value.trim(),
        smtp_from_name:           document.getElementById('set-smtp-from-name').value.trim(),
        smtp_use_tls:             document.getElementById('set-smtp-tls').checked,
      }),
    });
    const data = await res.json();
    if (!res.ok || !data.ok) throw new Error(data.detail || 'Failed');
    const extra = data.applied?.length ? `  (${data.applied.join(', ')})` : '';
    st.textContent = 'Settings saved.' + extra;
    st.className = 'save-status ok';
  } catch (e) {
    st.textContent = 'Saved locally — backend error: ' + e.message;
    st.className = 'save-status error';
  }
  setTimeout(() => { st.textContent = ''; }, 4000);
});

[
  ['btn-close-settings',  'modal-settings'],
  ['btn-close-settings2', 'modal-settings'],
].forEach(([btnId, modalId]) => {
  document.getElementById(btnId)?.addEventListener('click', () => _hideModal(modalId));
});

// ── MODAL RECORDINGS ──────────────────────────────────────────────────────
function openRecordingsModal() {
  const today = new Date().toISOString().slice(0, 10);
  document.getElementById('rec-filter-to').value = today;
  const from = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);
  document.getElementById('rec-filter-from').value = from;
  document.getElementById('modal-recordings-body').innerHTML =
    '<div style="color:var(--text-muted);font-size:0.75rem;padding:20px 0;text-align:center">Press SEARCH to load recordings.</div>';
  _showModal('modal-recordings');
}

document.getElementById('btn-rec-search')?.addEventListener('click', async () => {
  const body = document.getElementById('modal-recordings-body');
  body.innerHTML = '<div style="color:var(--text-muted);padding:8px">Loading...</div>';
  try {
    const from  = document.getElementById('rec-filter-from').value;
    const to    = document.getElementById('rec-filter-to').value;
    const mmsi  = document.getElementById('rec-filter-mmsi').value.trim();
    let url = `${API}/recordings?from=${from}&to=${to}`;
    if (mmsi) url += `&mmsi=${encodeURIComponent(mmsi)}`;
    const res  = await fetch(url, { headers: { Authorization: `Bearer ${window.Auth?.token()}` } });
    if (!res.ok) throw new Error(await res.text());
    const recs = await res.json();
    if (!recs.length) {
      body.innerHTML = '<div style="color:var(--text-muted);font-size:0.75rem;padding:20px 0;text-align:center">No recordings found for the selected period.</div>';
      return;
    }
    body.innerHTML = `
      <table class="users-table">
        <thead><tr>
          <th>VESSEL</th><th>MMSI</th><th>AID</th>
          <th>START (UTC)</th><th>END (UTC)</th><th>MIN DIST</th>
        </tr></thead>
        <tbody>${recs.map(r => `
          <tr>
            <td>${r.vessel_nombre || '--'}</td>
            <td class="mono">${r.mmsi}</td>
            <td>${r.aid_nombre || '--'}</td>
            <td class="mono" style="font-size:0.68rem">${r.inicio_utc ? new Date(r.inicio_utc).toUTCString().slice(0,25) : '--'}</td>
            <td class="mono" style="font-size:0.68rem">${r.fin_utc   ? new Date(r.fin_utc).toUTCString().slice(0,25)   : 'ACTIVE'}</td>
            <td>${r.distancia_min_m != null ? r.distancia_min_m + ' m' : '--'}</td>
          </tr>`).join('')}
        </tbody>
      </table>`;
  } catch (e) {
    body.innerHTML = `<div class="modal-error" style="display:block">Error: ${e.message}</div>`;
  }
});

[
  ['btn-close-recordings',  'modal-recordings'],
  ['btn-close-recordings2', 'modal-recordings'],
].forEach(([btnId, modalId]) => {
  document.getElementById(btnId)?.addEventListener('click', () => _hideModal(modalId));
});

// ── MODAL LAMP CATALOG ────────────────────────────────────────────────────
window._lampCache = [];   // exposed so the right panel can populate its dropdown

function _lampThresholds(vmin, vmax, warn_pct, alarm_pct) {
  const rng = vmax - vmin;
  const wp  = ((warn_pct  ?? 20) / 100);
  const ap  = ((alarm_pct ?? 10) / 100);
  return { warn: +(vmin + rng * wp).toFixed(3), alarm: +(vmin + rng * ap).toFixed(3) };
}

async function loadLamps() {
  try {
    const r = await fetch(`${API}/lamps/`);
    window._lampCache = await r.json();
  } catch { window._lampCache = []; }
  return window._lampCache;
}
window.loadLamps = loadLamps;

async function openLampsModal() {
  await loadLamps();
  renderLampsTable();
  document.getElementById('lamps-status').textContent = '';
  _showModal('modal-lamps');
}

function renderLampsTable() {
  const tbody = document.getElementById('lamps-body');
  tbody.innerHTML = window._lampCache.map(l => `
    <tr data-id="${l.id}">
      <td>${l.manufacturer}</td>
      <td>${l.model}</td>
      <td>${l.lamp_count}</td>
      <td>${l.voltage_min}</td>
      <td>${l.voltage_max}</td>
      <td style="color:var(--yellow)">${l.warn_v} V</td>
      <td style="color:var(--red)">${l.alarm_v} V</td>
      <td style="font-size:0.7rem;color:var(--text-muted)">${l.notes || ''}</td>
      <td style="display:flex;gap:4px">
        <button class="chart-row-btn" onclick="editLamp('${l.id}')">EDIT</button>
        <button class="chart-row-btn danger" onclick="deleteLamp('${l.id}')">DEL</button>
      </td>
    </tr>`).join('');
}

window.editLamp = function(id) {
  const l = window._lampCache.find(x => x.id === id);
  if (!l) return;
  const tr = document.querySelector(`#lamps-body tr[data-id="${id}"]`);
  if (!tr) return;
  tr.innerHTML = `
    <td><input class="form-input" id="lp-edit-mfr"   value="${l.manufacturer}"></td>
    <td><input class="form-input" id="lp-edit-model" value="${l.model}"></td>
    <td><input class="form-input" id="lp-edit-count" type="number" value="${l.lamp_count}" style="width:60px"></td>
    <td><input class="form-input" id="lp-edit-vmin"  type="number" step="0.1" value="${l.voltage_min}" style="width:70px"></td>
    <td><input class="form-input" id="lp-edit-vmax"  type="number" step="0.1" value="${l.voltage_max}" style="width:70px"></td>
    <td><input class="form-input" id="lp-edit-wpct"  type="number" step="1" min="1" max="50" value="${l.warn_pct ?? 20}" style="width:55px" title="Warn %"></td>
    <td><input class="form-input" id="lp-edit-apct"  type="number" step="1" min="1" max="50" value="${l.alarm_pct ?? 10}" style="width:55px" title="Alarm %"></td>
    <td style="font-size:0.7rem" id="lp-edit-preview">${l.warn_v} V / ${l.alarm_v} V</td>
    <td><input class="form-input" id="lp-edit-notes" value="${l.notes || ''}"></td>
    <td style="display:flex;gap:4px">
      <button class="chart-row-btn" onclick="saveLamp('${id}')">SAVE</button>
      <button class="chart-row-btn danger" onclick="renderLampsTable()">CANCEL</button>
    </td>`;
  // Live preview while editing vmin/vmax
  ['lp-edit-vmin','lp-edit-vmax','lp-edit-wpct','lp-edit-apct'].forEach(i =>
    document.getElementById(i).addEventListener('input', () => {
      const vmin = parseFloat(document.getElementById('lp-edit-vmin').value);
      const vmax = parseFloat(document.getElementById('lp-edit-vmax').value);
      const wpct = parseFloat(document.getElementById('lp-edit-wpct').value);
      const apct = parseFloat(document.getElementById('lp-edit-apct').value);
      const cell = document.getElementById('lp-edit-preview');
      if (isNaN(vmin) || isNaN(vmax) || vmax <= vmin) { cell.textContent = '—'; return; }
      const t = _lampThresholds(vmin, vmax, wpct, apct);
      cell.innerHTML = `<span style="color:var(--yellow)">${t.warn} V</span> / <span style="color:var(--red)">${t.alarm} V</span>`;
    }));
};

window.saveLamp = async function(id) {
  const status = document.getElementById('lamps-status');
  const payload = {
    manufacturer: document.getElementById('lp-edit-mfr').value.trim(),
    model:        document.getElementById('lp-edit-model').value.trim(),
    lamp_count:   parseInt(document.getElementById('lp-edit-count').value) || 1,
    voltage_min:  parseFloat(document.getElementById('lp-edit-vmin').value),
    voltage_max:  parseFloat(document.getElementById('lp-edit-vmax').value),
    warn_pct:     parseFloat(document.getElementById('lp-edit-wpct').value) || 20.0,
    alarm_pct:    parseFloat(document.getElementById('lp-edit-apct').value) || 10.0,
    notes:        document.getElementById('lp-edit-notes').value.trim() || null,
  };
  if (!payload.manufacturer || !payload.model || isNaN(payload.voltage_min) || isNaN(payload.voltage_max)) {
    status.textContent = 'Manufacturer, model, V min and V max are required.';
    status.className = 'save-status error'; return;
  }
  try {
    const r = await fetch(`${API}/lamps/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    const d = await r.json();
    if (!r.ok) throw new Error(d.detail || 'Failed');
    await loadLamps(); renderLampsTable();
    status.textContent = `Updated ${d.manufacturer} ${d.model}`;
    status.className = 'save-status ok';
  } catch (e) {
    status.textContent = 'Error: ' + e.message;
    status.className = 'save-status error';
  }
};

// Live preview of warn/alarm as the user types vmin/vmax in the new-row inputs
function _updateLampPreview() {
  const vmin = parseFloat(document.getElementById('lp-new-vmin').value);
  const vmax = parseFloat(document.getElementById('lp-new-vmax').value);
  const cell = document.getElementById('lp-new-preview');
  if (isNaN(vmin) || isNaN(vmax) || vmax <= vmin) { cell.textContent = '—'; return; }
  const t = _lampThresholds(vmin, vmax);
  cell.innerHTML = `<span style="color:var(--yellow)">${t.warn} V</span> / <span style="color:var(--red)">${t.alarm} V</span>`;
}
['lp-new-vmin', 'lp-new-vmax'].forEach(id => {
  document.getElementById(id)?.addEventListener('input', _updateLampPreview);
});

document.getElementById('btn-lamp-add')?.addEventListener('click', async () => {
  const status = document.getElementById('lamps-status');
  const payload = {
    manufacturer: document.getElementById('lp-new-mfr').value.trim(),
    model:        document.getElementById('lp-new-model').value.trim(),
    lamp_count:   parseInt(document.getElementById('lp-new-count').value) || 1,
    voltage_min:  parseFloat(document.getElementById('lp-new-vmin').value),
    voltage_max:  parseFloat(document.getElementById('lp-new-vmax').value),
    notes:        document.getElementById('lp-new-notes').value.trim() || null,
  };
  if (!payload.manufacturer || !payload.model || isNaN(payload.voltage_min) || isNaN(payload.voltage_max)) {
    status.textContent = 'Manufacturer, model, V min and V max are required.';
    status.className = 'save-status error'; return;
  }
  try {
    const r = await fetch(`${API}/lamps/`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    const d = await r.json();
    if (!r.ok) throw new Error(d.detail || 'Failed');
    // Reset row + refresh
    ['lp-new-mfr','lp-new-model','lp-new-vmin','lp-new-vmax','lp-new-notes'].forEach(i => document.getElementById(i).value = '');
    document.getElementById('lp-new-count').value = '1';
    document.getElementById('lp-new-preview').textContent = '—';
    await loadLamps(); renderLampsTable();
    status.textContent = `Added ${d.manufacturer} ${d.model}`;
    status.className = 'save-status ok';
  } catch (e) {
    status.textContent = 'Error: ' + e.message;
    status.className = 'save-status error';
  }
});

window.deleteLamp = async function(id) {
  if (!confirm('Delete this lamp from the catalog?')) return;
  const status = document.getElementById('lamps-status');
  try {
    let r = await fetch(`${API}/lamps/${id}`, { method: 'DELETE' });
    if (r.status === 409) {
      // Lamp is in use — ask user if they want to unassign and delete anyway
      const d = await r.json();
      const ok = confirm(`${d.detail}.\n\nUnassign from those aids and delete anyway?`);
      if (!ok) return;
      r = await fetch(`${API}/lamps/${id}?force=true`, { method: 'DELETE' });
    }
    const d = await r.json();
    if (!r.ok) throw new Error(d.detail || 'Failed');
    await loadLamps(); renderLampsTable();
    status.textContent = d.unassigned_from
      ? `Deleted (unassigned from ${d.unassigned_from} aid(s)).`
      : 'Deleted.';
    status.className = 'save-status ok';
  } catch (e) {
    status.textContent = 'Error: ' + e.message;
    status.className = 'save-status error';
  }
};

[
  ['btn-close-lamps',  'modal-lamps'],
  ['btn-close-lamps2', 'modal-lamps'],
].forEach(([btnId, modalId]) => {
  document.getElementById(btnId)?.addEventListener('click', () => _hideModal(modalId));
});

// Pre-load lamps once at startup so the right panel dropdown is ready immediately.
loadLamps();

// ── MODAL CONTACTS CATALOG ────────────────────────────────────────────────
window._contactsCache = [];

async function loadContacts() {
  try {
    const r = await fetch(`${API}/contacts/`, {
      headers: { Authorization: `Bearer ${window.Auth?.token()}` },
    });
    window._contactsCache = await r.json();
  } catch { window._contactsCache = []; }
  return window._contactsCache;
}
window.loadContacts = loadContacts;

window.openContactsModal = async function openContactsModal() {
  await loadContacts();
  renderContactsTable();
  document.getElementById('contacts-status').textContent = '';
  _showModal('modal-contacts');
}

function renderContactsTable() {
  const tbody = document.getElementById('contacts-body');
  tbody.innerHTML = window._contactsCache.map(c => {
    const match = c.role === 'PORT_AUTHORITY' ? c.port_name : c.company_name;
    const roleColor = c.role === 'PORT_AUTHORITY' ? 'var(--accent)' : 'var(--cyan)';
    return `
      <tr data-id="${c.id}">
        <td><span style="color:${roleColor};font-size:0.7rem">${c.role === 'PORT_AUTHORITY' ? 'PORT' : 'OWNER'}</span></td>
        <td>${c.name}</td>
        <td>${match || '<span style="color:var(--red)">—</span>'}</td>
        <td style="font-size:0.7rem">${c.email || '—'}</td>
        <td style="font-size:0.7rem">${c.phone || '—'}</td>
        <td style="font-size:0.7rem">${c.whatsapp || '—'}</td>
        <td style="font-size:0.7rem">${c.preferred_channel}</td>
        <td style="display:flex;gap:4px">
          <button class="chart-row-btn" onclick="editContact('${c.id}')">EDIT</button>
          <button class="chart-row-btn danger" onclick="deleteContact('${c.id}')">DEL</button>
        </td>
      </tr>`;
  }).join('');
}

window.editContact = function(id) {
  const c = window._contactsCache.find(x => x.id === id);
  if (!c) return;
  const tr = document.querySelector(`#contacts-body tr[data-id="${id}"]`);
  if (!tr) return;
  const match = c.role === 'PORT_AUTHORITY' ? c.port_name : c.company_name;
  tr.innerHTML = `
    <td>
      <select class="form-input-select" id="ct-edit-role">
        <option value="PORT_AUTHORITY" ${c.role === 'PORT_AUTHORITY' ? 'selected' : ''}>PORT AUTH</option>
        <option value="OWNER" ${c.role === 'OWNER' ? 'selected' : ''}>OWNER</option>
      </select>
    </td>
    <td><input class="form-input" id="ct-edit-name"  value="${c.name}"></td>
    <td><input class="form-input" id="ct-edit-match" value="${match || ''}"></td>
    <td><input class="form-input" id="ct-edit-email" value="${c.email || ''}"></td>
    <td><input class="form-input" id="ct-edit-phone" value="${c.phone || ''}"></td>
    <td><input class="form-input" id="ct-edit-whatsapp" value="${c.whatsapp || ''}"></td>
    <td>
      <select class="form-input-select" id="ct-edit-pref">
        <option value="EMAIL"    ${c.preferred_channel === 'EMAIL'    ? 'selected' : ''}>EMAIL</option>
        <option value="WHATSAPP" ${c.preferred_channel === 'WHATSAPP' ? 'selected' : ''}>WHATSAPP</option>
        <option value="SMS"      ${c.preferred_channel === 'SMS'      ? 'selected' : ''}>SMS</option>
      </select>
    </td>
    <td style="display:flex;gap:4px">
      <button class="chart-row-btn" onclick="saveContact('${id}')">SAVE</button>
      <button class="chart-row-btn danger" onclick="renderContactsTable()">CANCEL</button>
    </td>`;
};

window.saveContact = async function(id) {
  const status = document.getElementById('contacts-status');
  const role     = document.getElementById('ct-edit-role').value;
  const name     = document.getElementById('ct-edit-name').value.trim();
  const match    = document.getElementById('ct-edit-match').value.trim();
  const email    = document.getElementById('ct-edit-email').value.trim();
  const phone    = document.getElementById('ct-edit-phone').value.trim();
  const whatsapp = document.getElementById('ct-edit-whatsapp').value.trim();
  const pref     = document.getElementById('ct-edit-pref').value;
  if (!name || !match) {
    status.textContent = 'Name and Port/Company are required.';
    status.className = 'save-status error'; return;
  }
  const payload = {
    role, name,
    email: email || null,
    phone: phone || null,
    whatsapp: whatsapp || null,
    preferred_channel: pref,
    port_name:    role === 'PORT_AUTHORITY' ? match : null,
    company_name: role === 'OWNER'          ? match : null,
  };
  try {
    const r = await fetch(`${API}/contacts/${id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${window.Auth?.token()}`,
      },
      body: JSON.stringify(payload),
    });
    const d = await r.json();
    if (!r.ok) throw new Error(d.detail || 'Failed');
    await loadContacts(); renderContactsTable();
    status.textContent = `Updated ${d.name}`;
    status.className = 'save-status ok';
  } catch (e) {
    status.textContent = 'Error: ' + e.message;
    status.className = 'save-status error';
  }
};

document.getElementById('btn-contact-add')?.addEventListener('click', async () => {
  const status = document.getElementById('contacts-status');
  const role     = document.getElementById('ct-new-role').value;
  const name     = document.getElementById('ct-new-name').value.trim();
  const match    = document.getElementById('ct-new-match').value.trim();
  const email    = document.getElementById('ct-new-email').value.trim();
  const phone    = document.getElementById('ct-new-phone').value.trim();
  const whatsapp = document.getElementById('ct-new-whatsapp').value.trim();
  const pref     = document.getElementById('ct-new-pref').value;
  if (!name || !match) {
    status.textContent = 'Name and Port/Company are required.';
    status.className = 'save-status error'; return;
  }
  const payload = {
    role, name,
    email: email || null,
    phone: phone || null,
    whatsapp: whatsapp || null,
    preferred_channel: pref,
    port_name:    role === 'PORT_AUTHORITY' ? match : null,
    company_name: role === 'OWNER'          ? match : null,
  };
  try {
    const r = await fetch(`${API}/contacts/`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${window.Auth?.token()}`,
      },
      body: JSON.stringify(payload),
    });
    const d = await r.json();
    if (!r.ok) throw new Error(d.detail || 'Failed');
    ['ct-new-name','ct-new-match','ct-new-email','ct-new-phone','ct-new-whatsapp']
      .forEach(i => document.getElementById(i).value = '');
    await loadContacts(); renderContactsTable();
    status.textContent = `Added ${d.name}`;
    status.className = 'save-status ok';
  } catch (e) {
    status.textContent = 'Error: ' + e.message;
    status.className = 'save-status error';
  }
});

window.deleteContact = async function(id) {
  if (!confirm('Delete this contact?')) return;
  const status = document.getElementById('contacts-status');
  try {
    const r = await fetch(`${API}/contacts/${id}`, {
      method: 'DELETE',
      headers: { Authorization: `Bearer ${window.Auth?.token()}` },
    });
    if (!r.ok) throw new Error((await r.json()).detail);
    await loadContacts(); renderContactsTable();
    status.textContent = 'Deleted.'; status.className = 'save-status ok';
  } catch (e) {
    status.textContent = 'Error: ' + e.message;
    status.className = 'save-status error';
  }
};

[
  ['btn-close-contacts',  'modal-contacts'],
  ['btn-close-contacts2', 'modal-contacts'],
].forEach(([btnId, modalId]) => {
  document.getElementById(btnId)?.addEventListener('click', () => _hideModal(modalId));
});

// ── MODAL REPORT ALERT ────────────────────────────────────────────────────
// Opened from each event card's REPORT button. Resolves recipients via the
// aid metadata (port authority + owner), lets the operator edit the message,
// then opens the chosen channel (mailto / wa.me / sms) and logs the report.

const _CHANNEL_LABEL = { EMAIL: 'EMAIL', SMS: 'SMS', WHATSAPP: 'WHATSAPP' };

window.openReportModal = async function(itemEl) {
  let alertObj;
  try { alertObj = JSON.parse(itemEl.dataset.alertJson); }
  catch { window.alert('Invalid alert data'); return; }

  document.getElementById('modal-report').dataset.alertId    = itemEl.dataset.alertId;
  document.getElementById('modal-report').dataset.alertJson  = itemEl.dataset.alertJson;

  const summary = document.getElementById('report-alert-summary');
  summary.innerHTML = `
    <strong>${alertObj.tipo || 'ALERT'}</strong>
    ${alertObj.subtipo ? ` — ${alertObj.subtipo}` : ''}<br>
    Aid: ${alertObj.aid_nombre || alertObj.aid_id || '—'}
    ${alertObj.mmsi ? ` &middot; MMSI ${alertObj.mmsi}` : ''}<br>
    <span style="color:var(--text-muted)">${alertObj.timestamp || new Date().toISOString()}</span>
  `;

  const recDiv = document.getElementById('report-recipients');
  recDiv.innerHTML = '<div style="color:var(--text-muted);font-size:0.78rem">Loading…</div>';
  document.getElementById('report-status').textContent = '';
  document.getElementById('report-message').value = 'Loading…';

  // Fetch full aid info + recipients in parallel
  const params = new URLSearchParams();
  if (alertObj.aid_id) params.set('aid_id', alertObj.aid_id);
  if (alertObj.mmsi)   params.set('mmsi', alertObj.mmsi);
  let aidFull = null;
  try {
    if (alertObj.aid_id) {
      const r = await fetch(`${API}/aids/${alertObj.aid_id}`, {
        headers: { Authorization: `Bearer ${window.Auth?.token()}` },
      });
      if (r.ok) aidFull = await r.json();
    }
  } catch {}

  let recipientsResp;
  try {
    const r = await fetch(`${API}/alerts/resolve-recipients?${params.toString()}`);
    recipientsResp = await r.json();
  } catch (e) {
    recipientsResp = { recipients: [] };
  }

  // Live ATON telemetry (voltage, last update, etc.)
  const aton = window.atonData?.[alertObj.mmsi || aidFull?.mmsi] || {};

  // Build a detailed default message from every source we have
  document.getElementById('report-message').value = _buildReportMessage(alertObj, aidFull, aton, recipientsResp.aid);

  // Split recipients by role so each section is explicit, and so the user
  // sees clearly when a role is unmatched (no contact for that port/owner).
  const portAuths = (recipientsResp.recipients || []).filter(c => c.role === 'PORT_AUTHORITY');
  const owners    = (recipientsResp.recipients || []).filter(c => c.role === 'OWNER');
  const aidInfo   = recipientsResp.aid || {};
  const sendAllBtn = recipientsResp.recipients?.length
    ? `<button class="chart-row-btn ch-all" style="margin-bottom:10px" onclick="sendReportToAllRecipients()">⚡ SEND TO SELECTED — uncheck below to skip a recipient</button>`
    : '';

  recDiv.innerHTML = `
    ${sendAllBtn}
    <div class="modal-section-label">PORT AUTHORITY${aidInfo.puerto_responsable ? ` — "${aidInfo.puerto_responsable}"` : ''}</div>
    ${portAuths.length
      ? portAuths.map(c => _renderRecipientRow(c)).join('')
      : `<div style="color:var(--yellow);font-size:0.72rem;padding:6px 10px;background:#1a1500;border-radius:3px;margin-bottom:8px">
          ⚠ ${aidInfo.puerto_responsable
              ? `No PORT_AUTHORITY contact configured for port "${aidInfo.puerto_responsable}". Add one in SETTINGS → Contacts.`
              : 'This aid has no puerto_responsable assigned. Edit the aid to set it.'}
        </div>`}

    <div class="modal-section-label" style="margin-top:8px">OWNER${aidInfo.empresa_responsable ? ` — "${aidInfo.empresa_responsable}"` : ''}</div>
    ${owners.length
      ? owners.map(c => _renderRecipientRow(c)).join('')
      : `<div style="color:var(--yellow);font-size:0.72rem;padding:6px 10px;background:#1a1500;border-radius:3px">
          ⚠ ${aidInfo.empresa_responsable
              ? `No OWNER contact configured for company "${aidInfo.empresa_responsable}". Add one in SETTINGS → Contacts.`
              : 'This aid has no empresa_responsable assigned. Edit the aid to set it.'}
        </div>`}
  `;

  _showModal('modal-report');
};

function _buildReportMessage(alertObj, aid, aton, aidLite) {
  const lines = [];
  lines.push(`╔══ ALERTA ${alertObj.tipo || ''}${alertObj.subtipo ? ' — ' + alertObj.subtipo : ''} ══╗`);
  lines.push(`Hora: ${alertObj.timestamp || new Date().toISOString()}`);
  lines.push('');

  // Aid identification
  const a = aid || {};
  lines.push('AYUDA A LA NAVEGACIÓN');
  lines.push(`  Nombre: ${a.nombre || alertObj.aid_nombre || '—'}`);
  if (a.mmsi || alertObj.mmsi) lines.push(`  MMSI:   ${a.mmsi || alertObj.mmsi}`);
  if (a.tipo)        lines.push(`  Tipo:   ${a.tipo} (${a.categoria || '—'})`);
  if (a.tipo_ais)    lines.push(`  AIS:    ${a.tipo_ais}`);

  // Positions
  lines.push('');
  lines.push('POSICIÓN');
  if (a.lat_nominal != null && a.lon_nominal != null)
    lines.push(`  Oficial S-57: ${a.lat_nominal.toFixed(5)}, ${a.lon_nominal.toFixed(5)}`);
  if (a.lat_actual != null && a.lon_actual != null)
    lines.push(`  Actual AIS:   ${a.lat_actual.toFixed(5)}, ${a.lon_actual.toFixed(5)}`);
  if (a.desplazamiento_m != null)
    lines.push(`  Desplazamiento: ${a.desplazamiento_m.toFixed(1)} m`);
  else if (alertObj.desplazamiento_m != null)
    lines.push(`  Desplazamiento: ${alertObj.desplazamiento_m} m`);
  if (a.radio_borneo_m) lines.push(`  Radio borneo: ${a.radio_borneo_m} m`);

  // Light characteristics
  if (a.caracteristica_luz || a.alcance_nm) {
    lines.push('');
    lines.push('CARACTERÍSTICAS LUMINOSAS');
    if (a.caracteristica_luz) lines.push(`  Luz:    ${a.caracteristica_luz}`);
    if (a.alcance_nm)         lines.push(`  Rango:  ${a.alcance_nm} NM`);
  }

  // Lamp + battery
  if (a.lamp || aton.voltage_v != null || alertObj.voltage_v != null) {
    lines.push('');
    lines.push('LÁMPARA Y BATERÍA');
    if (a.lamp) {
      lines.push(`  Modelo: ${a.lamp.manufacturer} ${a.lamp.model} (×${a.lamp.lamp_count})`);
      lines.push(`  Vmin/Vmax nominal: ${a.lamp.voltage_min} V / ${a.lamp.voltage_max} V`);
      lines.push(`  Umbral WARN: ${a.lamp.warn_v} V  ·  ALARM: ${a.lamp.alarm_v} V`);
    }
    const v = aton.voltage_v ?? alertObj.voltage_v;
    if (v != null) {
      lines.push(`  Voltaje actual: ${v} V` + (aton.last_update ? `  (${aton.last_update.slice(11,19)} UTC)` : ''));
    }
    if (aton.battery_low) lines.push(`  ⚠ Battery_low flag activo`);
    if (aton.light_ok === false) lines.push(`  ⚠ LIGHT FAULT reportado por AIS`);
  }

  // Responsibility
  lines.push('');
  lines.push('RESPONSABLES');
  lines.push(`  Puerto:  ${a.puerto_responsable || '(no asignado)'}`);
  lines.push(`  Empresa: ${a.empresa_responsable || '(no asignada)'}`);

  if (a.observaciones) {
    lines.push('');
    lines.push('OBSERVACIONES');
    lines.push(`  ${a.observaciones}`);
  }

  lines.push('');
  lines.push('Por favor verifique en sitio y confirme.');
  lines.push(`Reportado por: ${window.Auth?.session?.nombre || 'operator'}`);
  return lines.join('\n');
}

function _renderRecipientRow(c) {
  const safeName = c.name.replace(/'/g, "\\'");
  const channels = [];
  const has = (c.email ? 1 : 0) + (c.whatsapp ? 1 : 0) + (c.phone ? 1 : 0);
  if (c.email)    channels.push(`<button class="chart-row-btn ch-email" onclick="sendReport('${c.id}','${safeName}','EMAIL','${c.email}')">✉ EMAIL</button>`);
  if (c.whatsapp) channels.push(`<button class="chart-row-btn ch-whatsapp" onclick="sendReport('${c.id}','${safeName}','WHATSAPP','${c.whatsapp}')">🟢 WHATSAPP</button>`);
  if (c.phone)    channels.push(`<button class="chart-row-btn ch-sms" onclick="sendReport('${c.id}','${safeName}','SMS','${c.phone}')">☎ SMS</button>`);
  if (has > 1)    channels.push(`<button class="chart-row-btn ch-all" onclick="sendReportAll('${c.id}','${safeName}','${c.email||''}','${c.whatsapp||''}','${c.phone||''}')">⚡ ALL</button>`);
  const roleLabel = c.role === 'PORT_AUTHORITY' ? 'PORT AUTHORITY' : 'OWNER';
  const roleColor = c.role === 'PORT_AUTHORITY' ? 'var(--accent)' : 'var(--cyan)';
  return `
    <div class="report-recipient" data-contact-id="${c.id}" style="background:#0e1a2a;border:1px solid #1f3550;border-radius:3px;padding:8px 10px;margin-bottom:6px">
      <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
        <label style="display:flex;align-items:center;gap:8px;cursor:pointer">
          <input type="checkbox" class="rcp-check" checked
                 data-contact-id="${c.id}"
                 data-contact-name="${safeName}"
                 data-email="${c.email || ''}"
                 data-whatsapp="${c.whatsapp || ''}"
                 data-phone="${c.phone || ''}"
                 data-preferred="${c.preferred_channel}"
                 style="width:14px;height:14px;cursor:pointer">
          <span><strong>${c.name}</strong> <span style="color:${roleColor};font-size:0.7rem">(${roleLabel})</span></span>
        </label>
        <span style="color:var(--text-muted);font-size:0.7rem">preferred: ${_CHANNEL_LABEL[c.preferred_channel] || c.preferred_channel}</span>
      </div>
      <div style="font-size:0.72rem;color:var(--text-muted);margin-bottom:6px;padding-left:22px">
        ${c.email ? '✉ '+c.email+' ' : ''}${c.phone ? '☎ '+c.phone+' ' : ''}${c.whatsapp ? '🟢 '+c.whatsapp : ''}
      </div>
      <div style="display:flex;gap:6px;flex-wrap:wrap;padding-left:22px">${channels.join('') || '<span style="color:var(--text-muted);font-size:0.7rem">No channels configured</span>'}</div>
    </div>`;
}

window.sendReportToAllRecipients = function() {
  // Iterate the CURRENTLY-CHECKED recipient checkboxes synchronously so all
  // channel-opens happen inside the user-gesture tick (popup blocker happy).
  const modal    = document.getElementById('modal-report');
  const alertObj = JSON.parse(modal.dataset.alertJson);
  const message  = document.getElementById('report-message').value;
  const subject  = `[AidsMonitoring] ${alertObj.tipo || 'Alert'} — ${alertObj.aid_nombre || alertObj.aid_id || ''}`;

  const fires = [];
  document.querySelectorAll('.rcp-check:checked').forEach(cb => {
    const ch        = cb.dataset.preferred;
    const email     = cb.dataset.email;
    const whatsapp  = cb.dataset.whatsapp;
    const phone     = cb.dataset.phone;
    let recipient = null, channel = ch;
    if (ch === 'EMAIL')         recipient = email;
    else if (ch === 'WHATSAPP') recipient = whatsapp;
    else if (ch === 'SMS')      recipient = phone;
    if (!recipient) {                              // fallback to whatever is set
      if      (email)    { recipient = email;    channel = 'EMAIL'; }
      else if (whatsapp) { recipient = whatsapp; channel = 'WHATSAPP'; }
      else if (phone)    { recipient = phone;    channel = 'SMS'; }
    }
    if (recipient) fires.push({
      contactId:   cb.dataset.contactId,
      contactName: cb.dataset.contactName,
      channel, recipient,
    });
  });

  if (!fires.length) {
    document.getElementById('report-status').textContent = 'No recipients selected.';
    document.getElementById('report-status').className   = 'save-status error';
    return;
  }

  // Open every channel first (single tick), then log them async.
  // Also collect the URLs so we can offer manual click-through fallbacks
  // if Chrome's popup blocker swallowed one.
  const fallbacks = [];
  for (const f of fires) {
    const url = _openChannel(f.channel, f.recipient, subject, message);
    if (url) fallbacks.push({ ...f, url });
  }
  for (const f of fires) _logAndMarkReport(f.contactId, f.contactName, f.channel, f.recipient, alertObj, message);

  // Render fallback links so the operator can manually click any blocked one
  const status = document.getElementById('report-status');
  status.className = 'save-status ok';
  status.innerHTML = `Tried to open ${fires.length} channel(s).
    If some didn't open (Chrome popup blocker), click here:<br>
    ${fallbacks.map(f =>
      `<a href="${f.url}" target="_blank" rel="noopener" style="display:inline-block;margin:4px 6px 0 0;color:var(--accent);text-decoration:underline;font-size:0.72rem">
        ${f.channel} → ${f.contactName}
      </a>`).join('')}`;
};

window.sendReportAll = async function(contactId, contactName, email, whatsapp, phone) {
  // Open ALL channels first within the same user-gesture tick — browsers
  // allow multiple popups when triggered from a single click. Logging
  // happens after, async, so it never blocks the channel opening.
  const modal   = document.getElementById('modal-report');
  const alertObj = JSON.parse(modal.dataset.alertJson);
  const message = document.getElementById('report-message').value;
  const subject = `[AidsMonitoring] ${alertObj.tipo || 'Alert'} — ${alertObj.aid_nombre || alertObj.aid_id || ''}`;

  if (email)    _openChannel('EMAIL',    email,    subject, message);
  if (whatsapp) _openChannel('WHATSAPP', whatsapp, subject, message);
  if (phone)    _openChannel('SMS',      phone,    subject, message);

  // Audit log + UI badge for each — does not depend on opening order
  if (email)    _logAndMarkReport(contactId, contactName, 'EMAIL',    email,    alertObj, message);
  if (whatsapp) _logAndMarkReport(contactId, contactName, 'WHATSAPP', whatsapp, alertObj, message);
  if (phone)    _logAndMarkReport(contactId, contactName, 'SMS',      phone,    alertObj, message);
};

async function _logAndMarkReport(contactId, contactName, channel, recipient, alertObj, message) {
  let smtpSent = false, smtpDetail = '';
  try {
    const r = await fetch(`${API}/alerts/report`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${window.Auth?.token()}`,
      },
      body: JSON.stringify({
        alert_id:      alertObj.id,
        alert_tipo:    alertObj.tipo,
        alert_subtipo: alertObj.subtipo,
        aid_id:        alertObj.aid_id,
        aid_nombre:    alertObj.aid_nombre,
        mmsi:          alertObj.mmsi,
        contact_id:    contactId,
        contact_name:  contactName,
        channel, recipient, message,
      }),
    });
    if (r.ok) {
      const d = await r.json();
      smtpSent = !!d.smtp_sent;
      smtpDetail = d.smtp_detail || '';
    }
  } catch {}

  // EMAIL fallback: if backend SMTP didn't send, open the operator's mail client
  if (channel === 'EMAIL' && !smtpSent) {
    const subject = `[AidsMonitoring] ${alertObj.tipo || 'Alert'} — ${alertObj.aid_nombre || alertObj.aid_id || ''}`;
    _openChannel('EMAIL', recipient, subject, message);
  }

  const modal  = document.getElementById('modal-report');
  const itemEl = document.querySelector(`.event-item[data-alert-id="${modal.dataset.alertId}"]`);
  if (itemEl) {
    const badge = itemEl.querySelector('.ev-reported');
    badge.classList.remove('hidden');
    const ts  = new Date().toUTCString().slice(17,25);
    const usr = window.Auth?.session?.nombre || 'operator';
    const tag = (channel === 'EMAIL' && smtpSent) ? 'EMAIL ✉ SMTP' : channel;
    badge.innerHTML += `<div>✓ Reported to <strong>${contactName}</strong> via ${tag} — ${ts} by ${usr}</div>`;
    itemEl.classList.add('ev-reported-state');
  }
}

// Fire a channel using a synthetic anchor click. Browsers treat
// anchor.click() inside a user-gesture handler as user-initiated, so the
// popup blocker is more lenient than with window.open() — that's the only
// way to reliably fire multiple channels from one click. Returns the URL
// fired (so the caller can offer a fallback link if Chrome still blocks it).
function _openChannel(channel, recipient, subject, message) {
  let url = null;
  if (channel === 'EMAIL') {
    url = `mailto:${recipient}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
  } else if (channel === 'WHATSAPP') {
    const phone = recipient.replace(/[^0-9]/g, '');
    url = `https://web.whatsapp.com/send?phone=${phone}&text=${encodeURIComponent(message)}`;
  } else if (channel === 'SMS') {
    url = `sms:${recipient}?body=${encodeURIComponent(message)}`;
  }
  if (!url) return null;
  const a = document.createElement('a');
  a.href = url;
  a.target = '_blank';
  a.rel = 'noopener';
  document.body.appendChild(a);
  a.click();
  a.remove();
  return url;
}

window.sendReport = async function(contactId, contactName, channel, recipient) {
  const modal   = document.getElementById('modal-report');
  const alertObj = JSON.parse(modal.dataset.alertJson);
  const message = document.getElementById('report-message').value;
  const subject = `[AidsMonitoring] ${alertObj.tipo || 'Alert'} — ${alertObj.aid_nombre || alertObj.aid_id || ''}`;
  const status  = document.getElementById('report-status');

  // For EMAIL we POST first; if backend SMTP is configured, the email is
  // sent server-side and we skip mailto. WhatsApp/SMS still use anchors.
  if (channel !== 'EMAIL') {
    _openChannel(channel, recipient, subject, message);
  }

  // Log the report (audit trail)
  try {
    await fetch(`${API}/alerts/report`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${window.Auth?.token()}`,
      },
      body: JSON.stringify({
        alert_id:      alertObj.id,
        alert_tipo:    alertObj.tipo,
        alert_subtipo: alertObj.subtipo,
        aid_id:        alertObj.aid_id,
        aid_nombre:    alertObj.aid_nombre,
        mmsi:          alertObj.mmsi,
        contact_id:    contactId,
        contact_name:  contactName,
        channel:       channel,
        recipient:     recipient,
        message:       message,
      }),
    });
  } catch (e) {
    console.warn('Could not log report:', e);
  }

  // Mark the originating event card as REPORTED
  const itemEl = document.querySelector(`.event-item[data-alert-id="${modal.dataset.alertId}"]`);
  if (itemEl) {
    const badge = itemEl.querySelector('.ev-reported');
    badge.classList.remove('hidden');
    const ts  = new Date().toUTCString().slice(17,25);
    const usr = window.Auth?.session?.nombre || 'operator';
    const prev = badge.innerHTML;
    badge.innerHTML = (prev || '') + `<div>✓ Reported to <strong>${contactName}</strong> via ${channel} — ${ts} by ${usr}</div>`;
    itemEl.classList.add('ev-reported-state');
    // Hide the REPORT button after first send (still visible if more recipients)
  }

  status.textContent = `Sent to ${contactName} via ${channel}.`;
  status.className = 'save-status ok';
};

[
  ['btn-close-report',  'modal-report'],
  ['btn-close-report2', 'modal-report'],
].forEach(([btnId, modalId]) => {
  document.getElementById(btnId)?.addEventListener('click', () => _hideModal(modalId));
});

// ── STATUS BAR ────────────────────────────────────────────────────────────
function setDot(dotId, labelId, text, color) {
  const dot = document.getElementById(dotId);
  const lbl = document.getElementById(labelId);
  if (dot) { dot.className = `sb-dot ${color}`; }
  if (lbl) lbl.textContent = text;
}

function updateStatusBar() {
  const user = window.Auth?.session;
  const sbUser = document.getElementById('sb-user');
  if (sbUser) sbUser.textContent = user ? `${user.nombre}  ·  ${user.role}` : '';
}

// Poll /ais/stats every 5 s to show real signal state in status bar
async function _pollAisStats() {
  try {
    const r = await fetch('/ais/stats');
    const d = await r.json();
    const src = (d.ais_source || 'SIMULATOR').toUpperCase();
    if (src === 'SIMULATOR') {
      setDot('sb-dot-ais', 'sb-ais', 'AIS: SIMULATOR', 'yellow');
      return;
    }
    const msgs  = d.sentences_received ?? 0;
    const secs  = d.seconds_since_last;
    const hasSignal = d.signal === true;
    const running   = d.catcher_running;

    let label, color;
    if (!running) {
      label = `AIS: ${src} — no catcher`;
      color = 'grey';
    } else if (!hasSignal) {
      const ago = secs != null ? ` (${secs}s)` : ' (sin señal)';
      label = `AIS: ${src}${ago}`;
      color = 'red';
    } else {
      label = `AIS: ${src} ✓ ${msgs} msg`;
      color = 'green';
    }
    setDot('sb-dot-ais', 'sb-ais', label, color);
  } catch (e) {
    setDot('sb-dot-ais', 'sb-ais', 'AIS: ?', 'grey');
  }
}

document.addEventListener('DOMContentLoaded', () => {
  updateStatusBar();
  _pollAisStats();
  setInterval(_pollAisStats, 5000);
});

// Sync WS dot to ws-status indicator
const _origWsStatus = document.getElementById('ws-status');
if (_origWsStatus) {
  new MutationObserver(() => {
    const online = _origWsStatus.classList.contains('online');
    setDot('sb-dot-ws', 'sb-ws',
      `WS: ${online ? 'ONLINE' : 'OFFLINE'}`,
      online ? 'green' : 'grey');
  }).observe(_origWsStatus, { childList: true, attributes: true, classList: true });
}
