// Velonics Hub — MQTT client singleton
// Connects to EMQX Cloud via WebSocket TLS (wss://:8084).
// Exposes window.MQ — used by app.jsx, dashboard.jsx, detail.jsx.
// Must be loaded after mqtt.js CDN and before app.jsx.

(() => {
  const BROKER  = 'wss://k77f2d32.ala.us-east-1.emqxsl.com:8084/mqtt';
  const OPTIONS = {
    clientId:       `VelonicsOne_PWA_${Math.random().toString(16).slice(2, 8)}`,
    username:       'Admin',
    password:       'Admin',
    clean:          true,
    reconnectPeriod: 1000,
    connectTimeout:  12000,
    keepalive:       60,
  };

  let _client          = null;
  let _connected       = false;
  let _userId          = null;   // set via connect(userId) — for user-specific topics
  let _onTelemetry     = null;   // (deviceId, dataObj) => void
  let _onStatus        = null;   // (deviceId, 'online'|'offline') => void
  let _onAck           = null;   // (deviceId, {cmd, ok, ...}) => void
  let _onNotify        = null;   // (deviceId, {type, unread}) => void  [Issue 5]
  let _onShareNotify   = null;   // (data) => void  — new share invite notification
  let _onConnect       = null;   // () => void
  let _onDisconnect    = null;   // () => void

  function connect(userId) {
    if (!window.mqtt) { console.error('[MQ] mqtt.js not loaded'); return; }
    if (_client)      { console.warn('[MQ] Already connecting');  return; }
    _userId = userId || null;

    console.log('[MQ] Connecting to', BROKER);
    _client = window.mqtt.connect(BROKER, OPTIONS);

    _client.on('connect', () => {
      _connected = true;
      console.log('[MQ] Connected');
      // Wildcard: one subscription catches all devices
      _client.subscribe('velonics/+/telemetry', { qos: 1 });
      _client.subscribe('velonics/+/status',    { qos: 0 });
      _client.subscribe('velonics/+/ack',       { qos: 1 });
      _client.subscribe('velonics/+/notify',    { qos: 1 });  // Issue 5
      // User-specific topic for share notifications
      if (_userId) {
        _client.subscribe(`velonics/users/${_userId}/share_notify`, { qos: 0 });
        console.log(`[MQ] Subscribed to share_notify for user ${_userId}`);
      }
      if (_onConnect) _onConnect();
    });

    _client.on('reconnect', () => console.log('[MQ] Reconnecting…'));

    _client.on('offline', () => {
      _connected = false;
      console.log('[MQ] Offline');
      if (_onDisconnect) _onDisconnect();
    });

    _client.on('error', err => console.error('[MQ]', err.message));

    // mqtt.js passes the raw PUBLISH packet as the 3rd argument.
    // packet.retain is true when the broker is replaying a stored retained message
    // (i.e. the last value cached on the broker, potentially minutes old).
    // It is false for every live message — poll responses, periodic telemetry, etc.
    _client.on('message', (topic, payload, packet) => {
      // Handle user-specific share_notify topic first
      // topic format: velonics/users/{userId}/share_notify
      if (_userId && topic === `velonics/users/${_userId}/share_notify`) {
        if (_onShareNotify) {
          try { _onShareNotify(JSON.parse(payload.toString())); }
          catch (e) { console.warn('[MQ] Bad share_notify JSON on', topic); }
        }
        return;
      }

      // topic format: velonics/{deviceId}/{type}
      const parts    = topic.split('/');
      if (parts.length < 3) return;
      const deviceId = parts[1];
      const type     = parts[2];

      if (type === 'telemetry' && _onTelemetry) {
        // Silently drop retained telemetry — it is stale broker-cached data, not live.
        // Fresh poll responses and periodic publishes always arrive with retain=false.
        if (packet.retain) {
          console.log('[MQ] Dropped retained telemetry from', deviceId);
          return;
        }
        try { _onTelemetry(deviceId, JSON.parse(payload.toString())); }
        catch (e) { console.warn('[MQ] Bad JSON on', topic); }
      } else if (type === 'status' && _onStatus) {
        _onStatus(deviceId, payload.toString().trim());
      } else if (type === 'ack' && _onAck) {
        try { _onAck(deviceId, JSON.parse(payload.toString())); }
        catch (e) { console.warn('[MQ] Bad ACK JSON on', topic); }
      } else if (type === 'notify' && _onNotify) {
        // Issue 5: backend fires this when a new alert is inserted — PWA fetches on demand
        try { _onNotify(deviceId, JSON.parse(payload.toString())); }
        catch (e) { console.warn('[MQ] Bad notify JSON on', topic); }
      }
    });
  }

  // ── Publish helpers ────────────────────────────────────────────

  function _pub(topic, obj) {
    if (!_client || !_connected) {
      console.warn('[MQ] Not connected — command dropped');
      return;
    }
    _client.publish(topic, JSON.stringify(obj), { qos: 1 });
  }

  // { cmd: 'motor', value: true|false }
  // { cmd: 'mode',  value: 'auto'|'manual' }
  function publishCmd(deviceId, cmd, value) {
    _pub(`velonics/${deviceId}/cmd`, { cmd, value });
  }

  // Valve state is a single command carrying both channels
  function publishValveCmd(deviceId, inletOpen, outletOpen) {
    _pub(`velonics/${deviceId}/cmd`, { cmd: 'valve', inlet: inletOpen, outlet: outletOpen });
  }

  // Retain so the ESP32 gets it on reconnect
  function publishSetpoints(deviceId, sp) {
    if (!_client || !_connected) return;
    _client.publish(`velonics/${deviceId}/setpoints`, JSON.stringify(sp), { qos: 1, retain: true });
  }

  // Ask each device to publish telemetry immediately and resume 5s interval
  function pollDevices(deviceIds) {
    deviceIds.forEach(id => _pub(`velonics/${id}/cmd`, { cmd: 'poll' }));
  }

  // Tell each device to suspend periodic publishing (app is backgrounded/closed)
  function sleepDevices(deviceIds) {
    deviceIds.forEach(id => _pub(`velonics/${id}/cmd`, { cmd: 'sleep' }));
  }

  // ── Callback registration ──────────────────────────────────────
  function onTelemetry(cb)   { _onTelemetry   = cb; }
  function onStatus(cb)      { _onStatus      = cb; }
  function onAck(cb)         { _onAck         = cb; }
  function onNotify(cb)      { _onNotify      = cb; }  // Issue 5
  function onShareNotify(cb) { _onShareNotify = cb; }  // Device sharing
  function onConnect(cb)     { _onConnect     = cb; }
  function onDisconnect(cb)  { _onDisconnect  = cb; }
  function isConnected()     { return _connected; }

  window.MQ = { connect, publishCmd, publishValveCmd, publishSetpoints, pollDevices, sleepDevices, onTelemetry, onStatus, onAck, onNotify, onShareNotify, onConnect, onDisconnect, isConnected };
})();
