'use strict';

const MESHTASTIC_NUS = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
const MESHTASTIC_TX = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';
const MESHTASTIC_RX = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';
let meshBle = { device: null, server: null, rx: null, tx: null, status: 'off' };

function meshStatusEl() { return document.getElementById('meshtasticStatus'); }

function setMeshStatus(text, ok) {
  const wasOn = meshBle.status === 'on';
  meshBle.status = ok ? 'on' : 'off';
  window.meshBleLinked = () => meshBle.status === 'on';
  const el = meshStatusEl();
  if (el) {
    el.textContent = text;
    el.classList.toggle('ok', !!ok);
    el.classList.toggle('err', !ok && text !== '未連線');
  }
  if (window.updateHealthUI) updateHealthUI();
  if (window.refreshConnectivityMatrix) refreshConnectivityMatrix();
  if (ok && !wasOn) {
    if (window.flushOfflineQueue) flushOfflineQueue();
    if (window.flushSosQueue) flushSosQueue();
  }
}

function tryDecodeMeshText(buf) {
  if (!buf || !buf.length) return null;
  try {
    const t = new TextDecoder('utf-8', { fatal: true }).decode(buf).trim();
    if (t.length >= 1 && t.length <= 200 && /^[\x20-\x7E\u4e00-\u9fff\u3400-\u4dbf\uff00-\uffef]+$/.test(t)) return t;
  } catch {}
  return null;
}

function hashStr(s) {
  let h = 0;
  const str = String(s || '');
  for (let i = 0; i < str.length; i++) h = ((h << 5) - h) + str.charCodeAt(i) | 0;
  return h;
}

function onMeshBleNotify(ev) {
  const buf = new Uint8Array(ev.target.value.buffer);
  const loraOn = typeof state !== 'undefined' && (state.loraForward || state.loraAutoFallback);
  const text = tryDecodeMeshText(buf);
  if (loraOn && text && window.ingestLoraWire) {
    if (/^ACK\|/i.test(text)) {
      const parts = text.split('|');
      ingestLoraWire({ type: 'ACK', refId: parts[1] || '', to: parts[2] || undefined });
      setMeshStatus('LoRa→ACK', true);
      return;
    }
    if (/^SOS\|/i.test(text)) {
      const parts = text.split('|');
      const name = parts[1] || 'Meshtastic';
      const lat = parseFloat(parts[2]);
      const lon = parseFloat(parts[3]);
      const detail = parts.slice(4).join('|') || 'SOS';
      ingestLoraWire({
        type: 'SOS',
        name,
        sid: 'lora-' + Math.abs(hashStr(name)),
        text: '🆘 SOS ' + name + '：' + detail,
        lat: Number.isFinite(lat) ? lat : undefined,
        lon: Number.isFinite(lon) ? lon : undefined
      });
      setMeshStatus('LoRa→SOS', true);
      return;
    }
    if (/^TXT\|/i.test(text)) {
      const parts = text.split('|');
      const name = parts[1] || 'Meshtastic';
      const body = parts.slice(2).join('|');
      ingestLoraWire({ type: 'TEXT', name, sid: 'lora-' + Math.abs(hashStr(name)), text: body });
      setMeshStatus('LoRa→文字', true);
      return;
    }
    if (/^LOC\s/i.test(text)) {
      const m = text.match(/LOC\s+([-\d.]+),([-\d.]+)(?:\s+name=([^\s]+))?(?:\s+sid=([^\s]+))?/i);
      if (m) {
        ingestLoraWire({
          type: 'TELEMETRY',
          lat: parseFloat(m[1]),
          lon: parseFloat(m[2]),
          name: m[3] || 'Meshtastic',
          sid: m[4] || ('lora-' + Math.abs(hashStr(m[3] || 'Meshtastic'))),
          text
        });
        setMeshStatus('LoRa→位置', true);
        return;
      }
    }
    ingestLoraWire({ type: 'TEXT', text: text.startsWith('[LoRa]') ? text : '[LoRa] ' + text });
    setMeshStatus('LoRa→Mesh：' + text.slice(0, 24), true);
    return;
  }
  setMeshStatus('收到 LoRa 封包（開啟轉發或用 MQTT 橋接）', true);
}

async function meshtasticConnectBle() {
  if (!navigator.bluetooth) {
    const ios = /iPhone|iPad|iPod/i.test(navigator.userAgent || '');
    alert(ios
      ? 'iPhone／Safari 不支援 Web Bluetooth。\n請用 Android APK 的 LoRa BLE，或電腦跑 MQTT 橋接腳本。'
      : '此瀏覽器不支援 Web Bluetooth。Android Chrome 可試；完整同步請用 MQTT 橋接。');
    return;
  }
  try {
    setMeshStatus('連線中…', false);
    const device = await navigator.bluetooth.requestDevice({
      filters: [{ namePrefix: 'Meshtastic' }],
      optionalServices: [MESHTASTIC_NUS],
    });
    meshBle.device = device;
    device.addEventListener('gattserverdisconnected', () => setMeshStatus('藍牙已斷線', false));
    const server = await device.gatt.connect();
    meshBle.server = server;
    const svc = await server.getPrimaryService(MESHTASTIC_NUS);
    meshBle.rx = await svc.getCharacteristic(MESHTASTIC_RX);
    try {
      meshBle.tx = await svc.getCharacteristic(MESHTASTIC_TX);
    } catch {
      meshBle.tx = null;
    }
    await meshBle.rx.startNotifications();
    meshBle.rx.addEventListener('characteristicvaluechanged', onMeshBleNotify);
    setMeshStatus('已連線：' + (device.name || 'Meshtastic'), true);
    alert('Meshtastic 藍牙已連線（可收發文字／位置標籤）。\n\niPhone 請改用 Android APK BLE 或 MQTT。');
  } catch (e) {
    if (e.name === 'NotFoundError') setMeshStatus('未選擇裝置', false);
    else setMeshStatus('連線失敗：' + (e.message || e), false);
  }
}

function meshtasticDisconnectBle() {
  try {
    if (meshBle.device && meshBle.device.gatt.connected) meshBle.device.gatt.disconnect();
  } catch {}
  meshBle = { device: null, server: null, rx: null, tx: null, status: 'off' };
  setMeshStatus('未連線', false);
}

/** Send UTF-8 text over NUS TX (best-effort). */
async function meshtasticSendText(text) {
  if (!meshBle.tx || meshBle.status !== 'on') return false;
  const payload = new TextEncoder().encode(String(text || '').slice(0, 180));
  try {
    await meshBle.tx.writeValue(payload);
    return true;
  } catch (e) {
    setMeshStatus('TX 失敗：' + (e.message || e), false);
    return false;
  }
}

/** TELEMETRY → LOC; SOS → SOS|…; TEXT → TXT|…; ACK → ACK|ref */
async function meshtasticSendWire(msg) {
  if (!msg || meshBle.status !== 'on') return false;
  if (msg.type === 'TELEMETRY' && msg.lat != null && msg.lon != null) {
    const name = (msg.name || 'web').replace(/\|/g, '/').slice(0, 32);
    const sid = (msg.sid || 'web').replace(/\|/g, '/').slice(0, 32);
    return meshtasticSendText(`LOC ${msg.lat},${msg.lon} name=${name} sid=${sid}`);
  }
  if (msg.type === 'ACK') {
    return meshtasticSendText(`ACK|${msg.refId || ''}|${msg.to || ''}`);
  }
  if (msg.type === 'SOS') {
    const name = (msg.name || 'web').replace(/\|/g, '/').slice(0, 32);
    const lat = msg.lat != null ? msg.lat : '';
    const lon = msg.lon != null ? msg.lon : '';
    const detail = String(msg.text || 'SOS').slice(0, 100);
    const ok = await meshtasticSendText(`SOS|${name}|${lat}|${lon}|${detail}`);
    if (ok && msg.lat != null && msg.lon != null) {
      await meshtasticSendText(`LOC ${msg.lat},${msg.lon} name=${name} sid=${(msg.sid || name).slice(0, 32)}`);
    }
    return ok;
  }
  if (msg.type === 'TEXT' || msg.type === 'RALLY' || msg.type === 'RALLY_CALL') {
    const name = (msg.name || 'web').replace(/\|/g, '/').slice(0, 32);
    const body = String(msg.text || msg.type).slice(0, 160);
    return meshtasticSendText(msg.type === 'TEXT' ? `TXT|${name}|${body}` : `${msg.type}|${body}`);
  }
  return false;
}

window.meshBleLinked = () => false;
window.meshtasticConnectBle = meshtasticConnectBle;
window.meshtasticDisconnectBle = meshtasticDisconnectBle;
window.meshtasticSendText = meshtasticSendText;
window.meshtasticSendWire = meshtasticSendWire;
