'use strict';
/* Main app logic: Qt bridge, GPS readout, waypoints, routes, navigation. */

// ── Modo de iluminación ────────────────────────────────────────────────────
function setMode(mode) {
  const modes = ['night','dusk','day','dayplus'];
  if (!modes.includes(mode)) return;
  // Aplica al html element (igual que ECDIS)
  if (mode === 'day') {
    document.documentElement.removeAttribute('data-mode');
  } else {
    document.documentElement.setAttribute('data-mode', mode);
  }
  // Marca botón activo
  modes.forEach(m => {
    const btn = document.getElementById('mode-' + m);
    if (btn) btn.classList.toggle('active', m === mode);
  });
  try { localStorage.setItem('gps-mode', mode); } catch(e) {}
  // Redibujar skyplot con la paleta del nuevo modo
  if (typeof SkyPlot !== 'undefined') SkyPlot.redraw();

  // ── Capas OSM + fondo canvas ─────────────────────────────────────────────
  // ECDIS correcto: las ayudas IALA NO se filtran — solo el fondo/OSM se oscurece.
  // El filtro CSS en #map fue eliminado (bug Qt5 WebEngine). En cambio:
  //   · osmLayer.opacity  → controla visibilidad de tiles OSM
  //   · #map background   → color de océano base en modo oscuro
  var _osmOp = { night: 0.12, dusk: 0.38, day: 0.82, dayplus: 0.90 };
  var _mapBg = { night: '#0a1018', dusk: '#101c30', day: '#a8c8e8', dayplus: '#b8d8f0' };
  if (typeof GPSMap !== 'undefined' && GPSMap.setOsmOpacity) {
    GPSMap.setOsmOpacity(_osmOp[mode] != null ? _osmOp[mode] : 0.82);
    GPSMap.setMapBackground(_mapBg[mode]);
  }

  // ── Paleta S-52 de capas ENC ─────────────────────────────────────────────
  // Recolorea DEPARE/LNDARE/DEPCNT según modo — ayudas IALA nunca se tocan.
  if (typeof ChartLayer !== 'undefined' && ChartLayer.setChartMode) {
    var encMode = mode === 'dayplus' ? 'day' : mode === 'day' ? 'day-std' : mode;
    ChartLayer.setChartMode(encMode);
  }
}

// Restaura modo al cargar (localStorage puede fallar en file://)
(function(){
  try {
    const saved = localStorage.getItem('gps-mode') || 'day';
    setMode(saved);
  } catch(e) {
    setMode('day');
  }
})();

// ── State ──────────────────────────────────────────────────────────────────
let _fix = {};
let _waypoints = [];   // [{id,name,lat,lon,notes,mark_type}]
let _routes    = [];   // [{id,name,wpt_ids}]
let _marks     = [];   // [{id,name,lat,lon,mark_type}]  — marcas POI (no son WPTs de ruta)
let _navWpt    = null; // active go-to waypoint
let _autoCenter = false;
let _chartLoadPending = false;  // trigger chart load on first GPS fix
let _pendingMarcaType = null;   // tipo de marca seleccionado en el modal, pendiente de click en mapa

// ── Helpers ────────────────────────────────────────────────────────────────
function _fmtDM(deg, hPos, hNeg) {
  if (deg == null) return '--°--\'-.--';
  const h = deg >= 0 ? hPos : hNeg;
  const a = Math.abs(deg);
  const d = Math.floor(a);
  const m = (a - d) * 60;
  return `${d}°${m.toFixed(3)}'${h}`;
}

function _fmtNum(v, dec, unit = '') {
  return v != null && !isNaN(v) ? Number(v).toFixed(dec) + unit : '--';
}

function _bearingTo(lat1, lon1, lat2, lon2) {
  const φ1 = lat1 * Math.PI/180, φ2 = lat2 * Math.PI/180;
  const Δλ = (lon2 - lon1) * Math.PI/180;
  const y = Math.sin(Δλ) * Math.cos(φ2);
  const x = Math.cos(φ1)*Math.sin(φ2) - Math.sin(φ1)*Math.cos(φ2)*Math.cos(Δλ);
  return (Math.atan2(y, x) * 180/Math.PI + 360) % 360;
}

function _distNM(lat1, lon1, lat2, lon2) {
  const R = 3440.065;  // NM
  const φ1 = lat1 * Math.PI/180, φ2 = lat2 * Math.PI/180;
  const Δφ = (lat2 - lat1) * Math.PI/180;
  const Δλ = (lon2 - lon1) * Math.PI/180;
  const a  = Math.sin(Δφ/2)**2 + Math.cos(φ1)*Math.cos(φ2)*Math.sin(Δλ/2)**2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
}

const FIX_NAMES = ['NO FIX','GPS','DGPS','PPS','RTK','Float RTK','DR','Manual','Simul.','WAAS'];
const FIX_MODE  = ['','','2D','3D'];

// ── GPS message handler (called from bridge.js via gpsMessage signal) ──────
window.handleGPSMsg = function(msg) {
  if (msg.type === 'connected') {
    document.getElementById('lbl-port').textContent = msg.port;
    document.getElementById('dot-gps').className = 'status-dot dot-ok';
  } else if (msg.type === 'no_port') {
    document.getElementById('lbl-port').textContent = 'NO GPS';
    document.getElementById('dot-gps').className = 'status-dot';
  } else if (msg.type === 'disconnected' || msg.type === 'error') {
    document.getElementById('lbl-port').textContent =
      msg.type === 'error' ? (msg.msg || 'ERROR') : 'DISCONNECTED';
    document.getElementById('dot-gps').className = 'status-dot dot-err';
  } else if (msg.type === 'position' || msg.type === 'rmc') {
    _updateFix(msg);
  } else if (msg.type === 'satellites') {
    SkyPlot.update(msg.sats);
  } else if (msg.type === 'dop') {
    _updateDOP(msg);
  } else if (msg.type === 'raw') {
    _appendNMEA(msg.sentence);
  } else if (msg.type === 'sensor') {
    if (typeof window.handleSensorMsg === 'function') window.handleSensorMsg(msg);
  }
};

// ── GPS readout ────────────────────────────────────────────────────────────
function _updateFix(msg) {
  Object.assign(_fix, msg);
  const lat = _fix.lat, lon = _fix.lon;
  const fq  = _fix.fix_quality ?? 0;

  document.getElementById('r-lat').textContent = _fmtDM(lat, 'N', 'S');
  document.getElementById('r-lon').textContent = _fmtDM(lon, 'E', 'W');
  document.getElementById('r-sog').textContent = _fmtNum(_fix.sog, 1, ' kn');
  document.getElementById('r-cog').textContent = _fmtNum(_fix.cog, 1, '°');
  document.getElementById('r-cogm').textContent= _fmtNum(_fix.cog_m, 1, '°M');
  document.getElementById('r-magvar').textContent = _fix.magvar != null
    ? (_fix.magvar >= 0 ? '+' : '') + _fix.magvar.toFixed(1) + '°' : '--';
  document.getElementById('r-alt').textContent = _fmtNum(_fix.altitude, 1, ' m');
  document.getElementById('r-hdop').textContent= _fmtNum(_fix.hdop, 1);
  document.getElementById('r-sats').textContent= _fix.satellites ?? '--';

  // Fix badge — colores: rojo=sin fix, verde=GPS, esmeralda=DGPS, cian=RTK
  const badge = document.getElementById('fix-badge');
  const fixName = FIX_NAMES[fq] || `FIX ${fq}`;
  badge.textContent = fixName;
  var fixCls = 'fix-none';
  if      (fq === 0)      fixCls = 'fix-none';
  else if (fq === 2 || fq === 9) fixCls = 'fix-dgps';   // DGPS / WAAS
  else if (fq >= 4)       fixCls = 'fix-great';          // RTK / Float RTK
  else                    fixCls = 'fix-ok';              // GPS normal (fq 1,3)
  badge.className = 'fix-badge ' + fixCls;

  // Map update
  if (lat != null && lon != null && fq > 0) {
    GPSMap.update(lat, lon, _fix.cog || 0, _fix.sog || 0);
    if (_autoCenter) GPSMap.centerOnGPS();
    _updateNavDisplay();
    // Primer fix GPS: cargar cartas para la posición actual.
    // Backup por si moveend llega antes de que _attachLayers registre el handler.
    if (_chartLoadPending && typeof ChartLayer !== 'undefined') {
      _chartLoadPending = false;
      setTimeout(function() { ChartLayer.loadAll(); }, 500);
    }
  }

  // Sky plot
  if (msg.sats) SkyPlot.update(msg.sats);
}

function _updateDOP(msg) {
  document.getElementById('r-hdop').textContent = _fmtNum(msg.hdop, 1);
  document.getElementById('r-vdop').textContent = _fmtNum(msg.vdop, 1);
  document.getElementById('r-pdop').textContent = _fmtNum(msg.pdop, 1);
  document.getElementById('r-fix').textContent  = FIX_MODE[msg.fix_mode] || '--';
}

// ── UTC clock ──────────────────────────────────────────────────────────────
setInterval(() => {
  const now = new Date();
  const utc = now.toISOString().replace('T',' ').substring(0,19) + ' UTC';
  document.getElementById('utc-clock').textContent = utc;
}, 1000);

// ── Navigation (go-to waypoint) ────────────────────────────────────────────
function startNav(wpt) {
  _navWpt = wpt;
  GPSMap.setActiveNav(wpt);
  _renderWaypoints();
  document.getElementById('nav-section').style.display = '';
  document.getElementById('nav-wpt-name').textContent = wpt.name;
  _updateNavDisplay();
}

function stopNav() {
  _navWpt = null;
  GPSMap.setActiveNav(null);
  _renderWaypoints();
  document.getElementById('nav-section').style.display = 'none';
}

function _updateNavDisplay() {
  if (!_navWpt || _fix.lat == null) return;
  const brg  = _bearingTo(_fix.lat, _fix.lon, _navWpt.lat, _navWpt.lon);
  const dist = _distNM(_fix.lat, _fix.lon, _navWpt.lat, _navWpt.lon);
  const sog  = _fix.sog || 0;
  const eta  = sog > 0.1 ? (dist / sog * 60).toFixed(0) + ' min' : '--';

  document.getElementById('nav-brg').textContent  = brg.toFixed(1) + '°';
  document.getElementById('nav-dist').textContent = dist.toFixed(2) + ' NM';
  document.getElementById('nav-eta').textContent  = eta;
  document.getElementById('nav-xte').textContent  = '--';
}

// ── Waypoints ──────────────────────────────────────────────────────────────
async function _loadWaypoints() {
  if (!window.py) return;
  var all = JSON.parse(await _py('get_waypoints'));
  // Separar WPTs de navegación (sin mark_type) de marcas POI (con mark_type)
  _waypoints = all.filter(w => !w.mark_type);
  _marks     = all.filter(w =>  w.mark_type);
  _renderWaypoints();
  _renderMapWaypoints();
  if (GPSMap && GPSMap.renderMarks) GPSMap.renderMarks(_marks);
  _renderMarksList();
}

function _renderWaypoints() {
  const el = document.getElementById('wpt-list');
  if (!el) return;
  if (!_waypoints.length) {
    el.innerHTML = '<div class="empty-list">No waypoints saved</div>';
    return;
  }
  el.innerHTML = _waypoints.map(w => {
    const dist = (_fix.lat != null) ? _distNM(_fix.lat, _fix.lon, w.lat, w.lon).toFixed(1) + ' NM' : '';
    const brg  = (_fix.lat != null) ? _bearingTo(_fix.lat, _fix.lon, w.lat, w.lon).toFixed(0) + '°' : '';
    const isActive = _navWpt && _navWpt.id === w.id;
    return `
      <div class="list-item ${isActive ? 'item-active' : ''} ${w.locked ? 'item-locked' : ''}">
        <div class="item-name">${w.name}</div>
        <div class="item-sub">${_fmtDM(w.lat,'N','S')}  ${_fmtDM(w.lon,'E','W')}</div>
        ${dist ? `<div class="item-nav">${brg}  ${dist}</div>` : ''}
        <div class="item-btns">
          <button class="icon-btn" onclick='startNav(${JSON.stringify(w)})' title="Ir a">▶</button>
          <button class="icon-btn" onclick='openWptModal(${JSON.stringify(w)})' title="Editar">✎</button>
          <button class="icon-btn icon-lock ${w.locked ? 'locked' : ''}" onclick='toggleWptLock("${w.id}")' title="${w.locked ? 'Desbloquear' : 'Bloquear'}">${w.locked ? '🔒' : '🔓'}</button>
          <button class="icon-btn icon-del" onclick='deleteWpt("${w.id}")' title="Eliminar">✕</button>
        </div>
      </div>`;
  }).join('');
}

function _renderMarksList() {
  var el = document.getElementById('mark-list');
  if (!el) return;
  if (!_marks.length) {
    el.innerHTML = '<div class="empty-list">No hay marcas guardadas</div>';
    return;
  }
  var MARK_DEFS = { fishing:'🎣', marina:'⚓', fuel:'⛽', restaurant:'🍴', dive:'🤿',
    anchorage:'🚢', beach:'🏖️', ramp:'🚤', repair:'🔧', hospital:'🏥',
    customs:'🛂', danger:'⚠️', hotel:'🏨', poi:'📍' };
  el.innerHTML = _marks.map(function(m) {
    var emoji = MARK_DEFS[m.mark_type] || '📍';
    return '<div class="list-item ' + (m.locked ? 'item-locked' : '') + '" style="display:flex;align-items:center;gap:6px">' +
      '<span class="mark-item-icon">' + emoji + '</span>' +
      '<div class="mark-item-body">' +
        '<div class="item-name">' + m.name + '</div>' +
        '<div class="item-sub">' + _fmtDM(m.lat,'N','S') + '  ' + _fmtDM(m.lon,'E','W') + '</div>' +
      '</div>' +
      '<div class="item-btns">' +
        '<button class="icon-btn" onclick=\'openMarkModal(' + JSON.stringify(m) + ')\' title="Editar">✎</button>' +
        '<button class="icon-btn icon-lock ' + (m.locked ? 'locked' : '') + '" onclick=\'toggleMarkLock("' + m.id + '")\' title="' + (m.locked ? 'Desbloquear' : 'Bloquear') + '">' + (m.locked ? '🔒' : '🔓') + '</button>' +
        '<button class="icon-btn icon-del" onclick=\'deleteMark("' + m.id + '")\' title="Eliminar">✕</button>' +
      '</div>' +
    '</div>';
  }).join('');
}

function _renderMapWaypoints() {
  GPSMap.renderWaypoints(_waypoints);
  const wptMap = Object.fromEntries(_waypoints.map(w => [w.id, w]));
  GPSMap.renderRoutes(_routes, wptMap);
}

window.onWptMapClick = function(wpt) {
  openWptModal(wpt);
};

// ── Map click handlers (draw modes) ───────────────────────────────────────
let _routeDraftPoints = [];
let _routeDraftName   = '';

window.onMapClickWpt = async function(lat, lon) {
  const n = _waypoints.length + 1;
  openWptModal({ lat, lon, name: `WPT ${String(n).padStart(3,'0')}` });
};

window.onMapClickRoute = async function(lat, lon, idx) {
  if (!window.py) return;
  const name = `RP${String(idx).padStart(2,'0')}`;
  const data = { name, lat, lon, notes: 'Route point' };
  const saved = JSON.parse(await _py('save_waypoint', JSON.stringify(data)));
  _routeDraftPoints.push(saved);
  await _loadWaypoints();
  const btn = document.getElementById('btn-draw-route');
  if (btn) btn.textContent = `✔ RTE (${idx})`;
};

window.onMapDblClickRoute = async function(coords) {
  if (_routeDraftPoints.length < 2) {
    alert('Need at least 2 points for a route');
    _cancelDrawMode();
    return;
  }
  const name = prompt('Route name:', _routeDraftName || `Route ${_routes.length + 1}`);
  if (!name) { _cancelDrawMode(); return; }
  const wpt_ids = _routeDraftPoints.map(p => p.id);
  await _py('save_route', JSON.stringify({ name, wpt_ids }));
  _routeDraftPoints = [];
  _routeDraftName = '';
  _cancelDrawMode();
  await _loadRoutes();
  lpTab('rte');   // muestra el tab de rutas
};

// ── Draw mode toolbar ──────────────────────────────────────────────────────
function _cancelDrawMode() {
  GPSMap.cancelDraw();
  _routeDraftPoints = [];
  const btnWpt  = document.getElementById('btn-draw-wpt');
  const btnRte  = document.getElementById('btn-draw-route');
  if (btnWpt)  { btnWpt.classList.remove('active');  btnWpt.textContent = '✚ WPT'; }
  if (btnRte)  { btnRte.classList.remove('active');  btnRte.textContent = '✚ RTE'; }
}

function toggleDrawWpt() {
  if (GPSMap.getDrawMode() === 'wpt') { _cancelDrawMode(); return; }
  _cancelDrawMode();
  GPSMap.setDrawMode('wpt');
  const btn = document.getElementById('btn-draw-wpt');
  if (btn) { btn.classList.add('active'); btn.textContent = '✕ WPT'; }
}

function toggleDrawRoute() {
  if (GPSMap.getDrawMode() === 'route') {
    if (_routeDraftPoints.length >= 2) {
      window.onMapDblClickRoute([]);
    } else {
      _cancelDrawMode();
    }
    return;
  }
  _cancelDrawMode();
  _routeDraftPoints = [];
  GPSMap.setDrawMode('route');
  const btn = document.getElementById('btn-draw-route');
  if (btn) { btn.classList.add('active'); btn.textContent = '✔ RTE (0)'; }
}

document.addEventListener('keydown', e => {
  if (e.key === 'Escape' && GPSMap.getDrawMode() !== 'none') _cancelDrawMode();
});

// ── Waypoint modal ─────────────────────────────────────────────────────────
window.openWptModal = function(wpt = {}) {
  document.getElementById('wpt-id').value    = wpt.id || '';
  document.getElementById('wpt-name').value  = wpt.name || '';
  document.getElementById('wpt-lat').value   = wpt.lat != null ? wpt.lat.toFixed(6) : '';
  document.getElementById('wpt-lon').value   = wpt.lon != null ? wpt.lon.toFixed(6) : '';
  document.getElementById('wpt-notes').value = wpt.notes || '';
  showModal('modal-wpt');
};

function addWptFromGPS() {
  if (_fix.lat == null) { alert('No GPS fix'); return; }
  const n = _waypoints.length + 1;
  openWptModal({ lat: _fix.lat, lon: _fix.lon, name: `WPT ${String(n).padStart(3,'0')}` });
}

function addWptManual() { openWptModal(); }

async function saveWpt() {
  const name = document.getElementById('wpt-name').value.trim();
  const lat  = parseFloat(document.getElementById('wpt-lat').value);
  const lon  = parseFloat(document.getElementById('wpt-lon').value);
  if (!name || isNaN(lat) || isNaN(lon)) { alert('Name, lat and lon are required'); return; }
  const data = {
    id:    document.getElementById('wpt-id').value || undefined,
    name, lat, lon,
    notes: document.getElementById('wpt-notes').value.trim(),
  };
  await _py('save_waypoint', JSON.stringify(data));
  closeModal();
  await _loadWaypoints();
}

async function deleteWpt(id) {
  if (!confirm('Delete waypoint?')) return;
  window.py.delete_waypoint(id);  // void — fire and forget
  if (_navWpt && _navWpt.id === id) stopNav();
  await _loadWaypoints();
}

// ── Routes ─────────────────────────────────────────────────────────────────
async function _loadRoutes() {
  if (!window.py) return;
  _routes = JSON.parse(await _py('get_routes'));
  _renderRoutes();
  _renderMapWaypoints();
}

function _renderRoutes() {
  const el = document.getElementById('route-list');
  if (!el) return;
  if (!_routes.length) {
    el.innerHTML = '<div class="empty-list">No routes saved</div>';
    return;
  }
  const wptMap = Object.fromEntries(_waypoints.map(w => [w.id, w]));
  el.innerHTML = _routes.map(r => {
    const wpts = (r.wpt_ids || []).map(id => wptMap[id]?.name || id).join(' → ');
    return `
      <div class="list-item">
        <div class="item-name">${r.name}</div>
        <div class="item-sub">${wpts}</div>
        <div class="item-btns">
          <button class="icon-btn icon-del" onclick='deleteRoute("${r.id}")' title="Delete">✕</button>
        </div>
      </div>`;
  }).join('');
}

function newRoute() {
  document.getElementById('rte-name').value = '';
  document.getElementById('rte-id').value   = '';
  const sel = document.getElementById('rte-wpt-selector');
  sel.innerHTML = _waypoints.map(w => `
    <label class="rte-wpt-row">
      <input type="checkbox" value="${w.id}"> ${w.name}
      <span class="rte-wpt-sub">${_fmtDM(w.lat,'N','S')} ${_fmtDM(w.lon,'E','W')}</span>
    </label>`).join('');
  showModal('modal-route');
}

async function saveRoute() {
  const name = document.getElementById('rte-name').value.trim();
  if (!name) { alert('Name required'); return; }
  const checks  = document.querySelectorAll('#rte-wpt-selector input:checked');
  const wpt_ids = [...checks].map(c => c.value);
  if (wpt_ids.length < 2) { alert('Select at least 2 waypoints'); return; }
  const data = {
    id: document.getElementById('rte-id').value || undefined,
    name, wpt_ids,
  };
  await _py('save_route', JSON.stringify(data));
  closeModal();
  await _loadRoutes();
}

async function deleteRoute(id) {
  if (!confirm('Delete route?')) return;
  window.py.delete_route(id);  // void
  await _loadRoutes();
}

// ── NMEA log ───────────────────────────────────────────────────────────────
const _nmeaBuf = [];
function _escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function _appendNMEA(line) {
  const color = line.startsWith('$GP') || line.startsWith('$GN') ? '#4ade80'
              : line.startsWith('$GL') ? '#f87171'
              : line.startsWith('$GA') ? '#34d399'
              : '#94a3b8';
  // Escape the raw NMEA sentence before inserting into innerHTML to prevent XSS
  _nmeaBuf.push(`<span style="color:${color}">${_escapeHtml(line)}</span>`);
  if (_nmeaBuf.length > 200) _nmeaBuf.shift();
  // Actualiza en tiempo real solo si el tab NMEA está activo
  const nmeaContent = document.getElementById('lp-nmea');
  if (nmeaContent && !nmeaContent.classList.contains('hidden')) {
    const el = document.getElementById('nmea-log');
    if (el) { el.innerHTML = _nmeaBuf.join('\n'); el.scrollTop = el.scrollHeight; }
  }
}

// ── Port connect modal ─────────────────────────────────────────────────────
async function showConnectModal() {
  if (!window.py) { alert('Bridge not ready'); return; }
  const ports = JSON.parse(await _py('list_ports'));
  const sel = document.getElementById('sel-port');
  sel.innerHTML = ports.length
    ? ports.map(p => `<option value="${p.port}">${p.port} — ${p.desc}</option>`).join('')
    : '<option value="">No ports found</option>';
  showModal('modal-connect');
}

function doConnect() {
  const port = document.getElementById('sel-port').value;
  const baud = parseInt(document.getElementById('sel-baud').value);
  if (!port) return;
  window.py.connect_gps(port, baud);  // void — GPS connected msg arrives via signal
  document.getElementById('lbl-port').textContent = port;
  closeModal();
}

function doDisconnect() {
  window.py.disconnect_gps();  // void
  document.getElementById('lbl-port').textContent = 'DISCONNECTED';
  document.getElementById('dot-gps').className = 'status-dot dot-err';
  closeModal();
}

// ── Panel izquierdo — sistema de tabs ─────────────────────────────────────
const _LP_TABS = ['gps', 'wpt', 'rte', 'mrk', 'nmea'];

function lpTab(tab) {
  try {
    _LP_TABS.forEach(t => {
      const content = document.getElementById('lp-' + t);
      const btn     = document.getElementById('lptab-' + t);
      if (content) content.classList.toggle('hidden', t !== tab);
      if (btn)     btn.classList.toggle('active', t === tab);
    });
    // Acciones al abrir cada tab
    if (tab === 'wpt')  _renderWaypoints();
    if (tab === 'rte')  _renderRoutes();
    if (tab === 'mrk')  _renderMarksList();
    if (tab === 'nmea') {
      const el = document.getElementById('nmea-log');
      if (el && _nmeaBuf.length) {
        el.innerHTML = _nmeaBuf.join('\n');
        el.scrollTop = el.scrollHeight;
      }
    }
  } catch(e) {
    console.error('[lpTab]', e);
  }
}

// Compatibilidad: si algo llama togglePanel apunta al tab equivalente
function togglePanel(panelId) {
  if (panelId === 'panel-waypoints') lpTab('wpt');
  else if (panelId === 'panel-routes') lpTab('rte');
  else if (panelId === 'panel-nmea')   lpTab('nmea');
}

// ── Modal helpers ──────────────────────────────────────────────────────────
function showModal(id) {
  document.querySelectorAll('.modal').forEach(m => m.classList.add('hidden'));
  document.getElementById(id).classList.remove('hidden');
  document.getElementById('modal-overlay').classList.remove('hidden');
}

function closeModal() {
  document.getElementById('modal-overlay').classList.add('hidden');
}

document.getElementById('modal-overlay').addEventListener('click', e => {
  if (e.target === document.getElementById('modal-overlay')) closeModal();
});

// ── Track ──────────────────────────────────────────────────────────────────
async function _loadTrack() {
  if (!window.py) return;
  const pts = JSON.parse(await _py('get_track', 2000));
  GPSMap.loadTrack(pts);
}

// ── Charts ─────────────────────────────────────────────────────────────────
let _chartCells = [];

async function showChartsModal() {
  showModal('modal-charts');   // abre el modal siempre, aunque falle el refresh
  await _refreshChartCells();
}

async function _refreshChartCells() {
  const el = document.getElementById('chart-cell-list');
  if (!window.py) {
    if (el) el.innerHTML = '<div class="empty-list" style="color:#f87171">Bridge not ready — restart app</div>';
    return;
  }
  try {
    _chartCells = JSON.parse(await _py('get_chart_cells'));
    _renderChartCells();
  } catch(e) {
    console.error('[charts] refresh failed:', e);
    if (el) el.innerHTML = '<div class="empty-list" style="color:#f87171">Error: ' + e + '</div>';
  }
}

function _renderChartCells() {
  const el = document.getElementById('chart-cell-list');
  if (!el) return;
  if (!_chartCells.length) {
    el.innerHTML = '<div class="empty-list">No charts installed</div>';
    return;
  }
  el.innerHTML = _chartCells.map(c => {
    const bbox = c.bbox ? c.bbox.map(v => v.toFixed(2)).join(', ') : '--';
    return `
      <div class="list-item" style="min-width:0;max-width:100%;margin-bottom:4px">
        <div style="display:flex;justify-content:space-between;align-items:flex-start">
          <div>
            <span class="item-name">${c.id}</span>
            <span style="margin-left:6px;font-size:0.7rem;color:var(--muted)">${c.features} features</span>
          </div>
          <div style="display:flex;gap:4px;align-items:center">
            <select style="background:var(--bg3);border:1px solid var(--border);color:var(--text);font-size:0.68rem;border-radius:3px;padding:1px 4px"
              onchange="setChartRegion('${c.id}', this.value)">
              <option value="B" ${c.region==='B'?'selected':''}>IALA-B</option>
              <option value="A" ${c.region==='A'?'selected':''}>IALA-A</option>
            </select>
            <button class="icon-btn icon-del" onclick="deleteChart('${c.id}')">✕</button>
          </div>
        </div>
        <div class="item-sub" style="margin-top:2px">bbox: ${bbox}</div>
      </div>`;
  }).join('');
}

async function uploadChart() {
  if (!window.py) return;
  const btn = document.getElementById('btn-upload-chart');
  btn.textContent = 'OPENING…'; btn.disabled = true;
  try {
    const res = JSON.parse(await _py('open_chart_file_dialog'));
    let msg = '';
    if (res.installed && res.installed.length)
      msg += `Installed: ${res.installed.join(', ')}\n`;
    if (res.skipped && res.skipped.length)
      msg += `Already installed (skipped): ${res.skipped.join(', ')}\n`;
    if (res.errors && res.errors.length)
      msg += `Errors:\n` + res.errors.map(e => `  ${e.file}: ${e.error}`).join('\n');
    if (!msg) msg = 'No charts installed (dialog cancelled or no valid files).';
    if (res.installed && res.installed.length) {
      alert(msg.trim());
      await ChartLayer.reloadAll();
      await _refreshChartCells();
    }
  } catch(e) {
    alert('Upload error: ' + e);
  } finally {
    btn.textContent = 'UPLOAD'; btn.disabled = false;
  }
}

async function scanChartsPath() {
  if (!window.py) return;
  const inp  = document.getElementById('chart-path-inp');
  const path = inp.value.trim();
  if (!path) { alert('Enter a folder path (e.g. E:\\ENC_Charts)'); return; }

  const btn = document.getElementById('btn-scan-chart');
  btn.textContent = 'SCANNING…'; btn.disabled = true;
  try {
    const res = JSON.parse(await _py('scan_charts_path', path));
    let msg = '';
    if (res.installed && res.installed.length)
      msg += `Installed: ${res.installed.join(', ')}\n`;
    if (res.skipped && res.skipped.length)
      msg += `Already installed (skipped): ${res.skipped.join(', ')}\n`;
    if (res.errors && res.errors.length)
      msg += `Errors:\n` + res.errors.map(e => `  ${e.file}: ${e.error}`).join('\n');
    if (!msg) msg = 'No .000 or .zip chart files found in that folder.';
    alert(msg.trim());

    if (res.installed && res.installed.length) {
      await ChartLayer.reloadAll();
      await _refreshChartCells();
    }
  } catch(e) {
    alert('Scan error: ' + e);
  } finally {
    btn.textContent = 'SCAN'; btn.disabled = false;
  }
}

async function deleteChart(cellId) {
  if (!confirm(`Delete chart cell "${cellId}"?`)) return;
  window.py.delete_chart(cellId);  // void
  await ChartLayer.reloadAll();
  await _refreshChartCells();
}

async function setChartRegion(cellId, region) {
  await _py('set_chart_region', cellId, region);
  await ChartLayer.reloadAll();
}

// ── ENC layers modal (AVANZADO) ────────────────────────────────────────────
function openEncLayersModal() {
  ChartLayer.setDetailLevel('advanced');
  var adv = ChartLayer.getAdvLayers();
  // Profundidades
  document.getElementById('el-depare').checked   = adv.depare;
  document.getElementById('el-depcnt').checked   = adv.depcnt;
  document.getElementById('el-soundg').checked   = adv.soundg;
  // Peligros y zonas
  document.getElementById('el-hazards').checked  = adv.hazards;
  document.getElementById('el-zones').checked    = adv.zones;
  // Tierra y costa
  document.getElementById('el-coalne').checked   = adv.coalne;
  document.getElementById('el-landmask').checked = adv.landmask;
  document.getElementById('el-lndare').checked   = adv.lndare;
  document.getElementById('el-buaare').checked   = adv.buaare;
  // Mapa base
  document.getElementById('el-osm').checked      = adv.osm;
  showModal('modal-enc-layers');
}

function applyEncLayers() {
  ChartLayer.setLayerVisibility({
    // Profundidades
    depare:   document.getElementById('el-depare').checked,
    depcnt:   document.getElementById('el-depcnt').checked,
    soundg:   document.getElementById('el-soundg').checked,
    // Peligros y zonas
    hazards:  document.getElementById('el-hazards').checked,
    zones:    document.getElementById('el-zones').checked,
    // Tierra y costa
    coalne:   document.getElementById('el-coalne').checked,
    landmask: document.getElementById('el-landmask').checked,
    lndare:   document.getElementById('el-lndare').checked,
    buaare:   document.getElementById('el-buaare').checked,
    // Mapa base
    osm:      document.getElementById('el-osm').checked,
  });
  closeModal();
}

// ── Chart-under-cursor indicator ──────────────────────────────────────────
(function _initChartCursor() {
  /* Espera a que el mapa esté listo */
  function _setup() {
    if (!window.GPSMap || !GPSMap.getOLMap) return setTimeout(_setup, 500);
    const olMap = GPSMap.getOLMap();
    const info  = document.getElementById('map-chart-info');
    if (!info) return;

    let _hideTimer = null;
    olMap.on('pointermove', function (evt) {
      if (!_chartCells.length) return;
      const [lon, lat] = ol.proj.toLonLat(evt.coordinate);
      const hits = _chartCells.filter(function (c) {
        if (!c.bbox || c.bbox.length < 4) return false;
        return lon >= c.bbox[0] && lat >= c.bbox[1] && lon <= c.bbox[2] && lat <= c.bbox[3];
      });
      if (hits.length) {
        info.textContent = '⛵ ' + hits.map(function(c){ return c.id; }).join(' · ');
        info.classList.add('visible');
        clearTimeout(_hideTimer);
        _hideTimer = setTimeout(function(){ info.classList.remove('visible'); }, 3000);
      } else {
        clearTimeout(_hideTimer);
        info.classList.remove('visible');
      }
    });
  }
  _setup();
})();

// ── Sensor data (compass HDG, ecosonda depth/temp) ────────────────────────
window.handleSensorMsg = function (msg) {
  /* msg viene de bridge.py via gpsMessage con type:'sensor'
     Formatos esperados:
       HDT: {type:'sensor', src:'HDT', hdg_t: 123.4}
       HDM: {type:'sensor', src:'HDM', hdg_m: 123.4}
       DBT/DPT: {type:'sensor', src:'DBT', depth: 12.3}
       MTW: {type:'sensor', src:'MTW', water_temp: 28.5}  */
  if (msg.hdg_t  != null) { const e = document.getElementById('r-hdg-t');      if (e) e.textContent = msg.hdg_t.toFixed(1) + '°T'; }
  if (msg.hdg_m  != null) { const e = document.getElementById('r-hdg-m');      if (e) e.textContent = msg.hdg_m.toFixed(1) + '°M'; }
  if (msg.depth  != null) { const e = document.getElementById('r-depth');      if (e) e.textContent = msg.depth.toFixed(1) + ' m'; }
  if (msg.water_temp != null) { const e = document.getElementById('r-water-temp'); if (e) e.textContent = msg.water_temp.toFixed(1) + '°C'; }
};

// ── MARCAS POI ─────────────────────────────────────────────────────────────
var _selectedMarcaType = null;

window.openMarcaModal = function() {
  _selectedMarcaType = null;
  document.querySelectorAll('.marca-item').forEach(function(el) { el.classList.remove('selected'); });
  var hint = document.getElementById('marca-type-hint');
  if (hint) hint.textContent = '— Ningún tipo seleccionado —';
  var btn = document.getElementById('btn-marca-ok');
  if (btn) btn.disabled = true;
  showModal('modal-marca');
};

window.selectMarcaType = function(el) {
  document.querySelectorAll('.marca-item').forEach(function(e) { e.classList.remove('selected'); });
  el.classList.add('selected');
  _selectedMarcaType = el.getAttribute('data-type');
  var hint = document.getElementById('marca-type-hint');
  if (hint) hint.textContent = '✔ ' + el.querySelector('.marca-label').textContent + ' seleccionado';
  var btn = document.getElementById('btn-marca-ok');
  if (btn) btn.disabled = false;
};

window.startMarcaDraw = function() {
  if (!_selectedMarcaType) return;
  _pendingMarcaType = _selectedMarcaType;
  closeModal();
  // Activar modo de dibujo MARCA (reutiliza el draw-mode del mapa)
  GPSMap.setDrawMode('mark');
  var btn = document.getElementById('btn-draw-mark');
  if (btn) { btn.classList.add('active'); btn.textContent = '📍...'; }
};

// Callback cuando el usuario hace click en mapa en modo mark
window.onMapClickMark = async function(lat, lon) {
  if (!window.py || !_pendingMarcaType) return;
  var n = _marks.length + 1;
  var typeLabels = { fishing:'PESCA', marina:'MARINA', fuel:'COMBUST', restaurant:'REST',
    dive:'BUCEO', anchorage:'FONDEO', beach:'PLAYA', ramp:'RAMPA',
    repair:'TALLER', hospital:'EMERG', customs:'ADUANA',
    danger:'PELIGRO', hotel:'HOTEL', poi:'POI' };
  var prefix = typeLabels[_pendingMarcaType] || 'MARCA';
  var data = { name: prefix + String(n).padStart(2,'0'), lat, lon, mark_type: _pendingMarcaType, notes: '' };
  await _py('save_waypoint', JSON.stringify(data));
  await _loadWaypoints();
  // Salir del modo marca
  GPSMap.setDrawMode('none');
  var btn = document.getElementById('btn-draw-mark');
  if (btn) { btn.classList.remove('active'); btn.textContent = '📍MARCA'; }
  _pendingMarcaType = null;
};

// Drag de WPT — guarda nueva posición en backend
window.onWptDrag = async function(wpt) {
  if (!window.py) return;
  await _py('save_waypoint', JSON.stringify(wpt));
  // Re-renderizar rutas con las nuevas coordenadas
  var wptMap = Object.fromEntries(_waypoints.map(w => [w.id, w]));
  GPSMap.renderRoutes(_routes, wptMap);
};

// Drag de MARCA — guarda nueva posición en backend
window.onMarkDrag = async function(mark) {
  if (!window.py) return;
  await _py('save_waypoint', JSON.stringify(mark));
};

window.openMarkModal = function(m) {
  document.getElementById('mark-id').value    = m.id || '';
  document.getElementById('mark-name').value  = m.name || '';
  document.getElementById('mark-type-val').value = m.mark_type || 'poi';
  document.getElementById('mark-lat').value   = m.lat != null ? m.lat.toFixed(6) : '';
  document.getElementById('mark-lon').value   = m.lon != null ? m.lon.toFixed(6) : '';
  document.getElementById('mark-notes').value = m.notes || '';
  var MARK_DEFS = { fishing:'🎣 Pesca', marina:'⚓ Marina', fuel:'⛽ Combustible',
    restaurant:'🍴 Restaurante', dive:'🤿 Buceo', anchorage:'🚢 Fondeo',
    beach:'🏖️ Playa', ramp:'🚤 Rampa', repair:'🔧 Taller', hospital:'🏥 Emergencia',
    customs:'🛂 Aduana', danger:'⚠️ Peligro', hotel:'🏨 Hotel', poi:'📍 POI' };
  document.getElementById('mark-type-display').textContent = MARK_DEFS[m.mark_type] || '📍 POI';
  showModal('modal-mark');
};

window.saveMark = async function() {
  if (!window.py) return;
  var data = {
    id:        document.getElementById('mark-id').value || undefined,
    name:      document.getElementById('mark-name').value.trim(),
    lat:       parseFloat(document.getElementById('mark-lat').value),
    lon:       parseFloat(document.getElementById('mark-lon').value),
    notes:     document.getElementById('mark-notes').value.trim(),
    mark_type: document.getElementById('mark-type-val').value || 'poi',
  };
  if (!data.name || isNaN(data.lat) || isNaN(data.lon)) { alert('Nombre, lat y lon son requeridos'); return; }
  await _py('save_waypoint', JSON.stringify(data));
  closeModal();
  await _loadWaypoints();
};

window.deleteMark = async function(id) {
  if (!confirm('¿Eliminar esta marca?')) return;
  window.py.delete_waypoint(id);
  await _loadWaypoints();
};

window.toggleWptLock = async function(id) {
  if (!window.py) return;
  var wpt = _waypoints.find(function(w) { return w.id === id; });
  if (!wpt) return;
  wpt.locked = wpt.locked ? 0 : 1;
  await _py('save_waypoint', JSON.stringify(wpt));
  await _loadWaypoints();
};

window.toggleMarkLock = async function(id) {
  if (!window.py) return;
  var mark = _marks.find(function(m) { return m.id === id; });
  if (!mark) return;
  mark.locked = mark.locked ? 0 : 1;
  await _py('save_waypoint', JSON.stringify(mark));
  await _loadWaypoints();
};

window.onMarkMapClick = function(mark) {
  openMarkModal(mark);
};

// ── Boot (called by bridge.js once QWebChannel is ready) ──────────────────
window.bootApp = async function () {
  await _loadWaypoints();
  await _loadRoutes();
  await _loadTrack();
  // Carga inicial de cartas (mapa arranca en Miami por defecto).
  // moveend handler se activa dentro de loadAll() → carga automática al navegar.
  _chartLoadPending = true;   // backup: re-disparar al primer fix GPS si no hay celdas
  await ChartLayer.loadAll();
};
