(function(global) {
  'use strict';

  const state = {
    client: null,
    config: null,
    connected: false,
    handlers: [],
    connecting: null,
    connectionType: null
  };

  function unique(values) {
    const seen = {};
    return values.filter(function(value) {
      if (!value || seen[value]) return false;
      seen[value] = true;
      return true;
    });
  }

  function candidateUrls(url, port) {
    const base = String(url || '').trim();
    const candidates = [base];
    const configuredPort = parseInt(port, 10);

    try {
      const parsed = new URL(base);
      const origins = [parsed.origin];
      if (configuredPort && !parsed.port) {
        origins.push(parsed.protocol + '//' + parsed.hostname + ':' + configuredPort);
      }
      const normalizedPath = parsed.pathname.replace(/\/+$/g, '');

      origins.forEach(function(origin) {
        if (normalizedPath) {
          candidates.push(origin + normalizedPath);
          candidates.push(origin + normalizedPath + '/');
        }

        candidates.push(origin + '/ws');
        candidates.push(origin + '/ws/');
        candidates.push(origin + '/mqtt');
        candidates.push(origin + '/mqtt/');
      });
    } catch (_) {}

    return unique(candidates);
  }

  function trimSlashes(value) {
    return String(value || '').replace(/^\/+|\/+$/g, '');
  }

  function withPrefix(topic) {
    const prefix = trimSlashes(state.config && state.config.topicPrefix);
    const cleanTopic = trimSlashes(topic);
    if (!prefix) return cleanTopic;
    if (cleanTopic === prefix || cleanTopic.indexOf(prefix + '/') === 0) return cleanTopic;
    return prefix + '/' + cleanTopic;
  }

  function stripPrefix(topic) {
    const prefix = trimSlashes(state.config && state.config.topicPrefix);
    let cleanTopic = trimSlashes(topic);
    if (prefix && cleanTopic.indexOf(prefix + '/') === 0) cleanTopic = cleanTopic.slice(prefix.length + 1);
    return cleanTopic;
  }

  function updateIndicator(connected) {
    state.connected = !!connected;
    if (typeof global.updateWSIndicator === 'function') global.updateWSIndicator(state.connected);
  }

  function normalizeState(value) {
    const v = String(value || '').trim().toLowerCase();
    if (['online', 'connected', 'available', '1', 'true', 'on'].indexOf(v) !== -1) return 'online';
    if (['offline', 'disconnected', 'unavailable', '0', 'false', 'off'].indexOf(v) !== -1) return 'offline';
    return v || 'desconocido';
  }

  function decodePayload(message) {
    if (message && typeof message === 'object') return message;
    const text = String(message || '');
    try { return JSON.parse(text); } catch (_) { return text; }
  }

  function emit(event) {
    state.handlers.forEach(function(handler) {
      try { handler(event); } catch (err) { console.error('[MQTT realtime] handler error', err); }
    });
  }

  function handleMessage(topic, message) {
    const cleanTopic = stripPrefix(topic);
    const parts = cleanTopic.split('/');
    const deviceIndex = parts.indexOf('dispositivo');
    if (deviceIndex === -1 || parts.length < deviceIndex + 3) return;

    const chipid = String(parts[deviceIndex + 1] || '').toUpperCase();
    const type = parts[deviceIndex + 2];
    const subtype = parts[deviceIndex + 3] || null;
    const payload = decodePayload(message);
    const payloadText = typeof payload === 'string' ? payload : JSON.stringify(payload || '');

    if (type === 'state') {
      emit({ type: 'state', chipid: chipid, estado: normalizeState(payloadText), topic: cleanTopic });
      return;
    }

    if (type === 'ip') {
      emit({ type: 'ip', chipid: chipid, ip: payloadText.trim(), topic: cleanTopic });
      return;
    }

    if ((type === 'valores' || type === 'pin') && subtype) {
      const valor = payload && typeof payload === 'object' && payload.valor !== undefined ? payload.valor : payloadText;
      emit({ type: 'pin', chipid: chipid, alias: subtype, valor: String(valor), topic: cleanTopic });
      return;
    }

    if (type === 'ota' && subtype === 'status') {
      emit({ type: 'ota', chipid: chipid, status: payloadText.trim(), topic: cleanTopic });
      return;
    }

    if (type === 'comando' && payload && typeof payload === 'object' && payload.alias !== undefined && payload.valor !== undefined) {
      emit({ type: 'pin', chipid: chipid, alias: String(payload.alias), valor: String(payload.valor), topic: cleanTopic });
    }
  }

  function connect() {
    if (state.client && state.connected) return Promise.resolve(state.client);
    if (state.connecting) return state.connecting;

    state.connecting = fetch('/api/mqtt_config', { cache: 'no-store', credentials: 'same-origin', headers: { Accept: 'application/json' } })
      .then(function(r) { return r.json(); })
      .then(function(response) {
        state.config = response.data || response;

        if (!state.config.url) throw new Error('WS_URL no esta configurado.');

        const urls = candidateUrls(state.config.url, state.config.port);
        return connectNativeWebSocket(urls, 0);
    }).catch(function(err) {
      updateIndicator(false);
      console.error('[MQTT realtime] init error', err);
      throw err;
    }).finally(function() {
      state.connecting = null;
    });

    return state.connecting;
  }

  function connectNativeWebSocket(urls, index) {
    if (index >= urls.length) {
      throw new Error('No se pudo conectar a ningun endpoint WebSocket JSON.');
    }

    const url = urls[index];
    console.info('[MQTT realtime] conectando WebSocket JSON', { url: url });

    return new Promise(function(resolve, reject) {
      let settled = false;
      const socket = new WebSocket(url);
      const timer = setTimeout(function() {
        fail(new Error('timeout'));
      }, 7000);

      function fail(err) {
        if (settled) return;
        settled = true;
        clearTimeout(timer);
        try { socket.close(); } catch (_) {}
        console.warn('[MQTT realtime] fallo WebSocket JSON', url, err && (err.message || err));
        connectNativeWebSocket(urls, index + 1).then(resolve).catch(reject);
      }

      socket.addEventListener('open', function() {
        if (settled) return;
        settled = true;
        clearTimeout(timer);
        state.client = socket;
        state.connectionType = 'websocket-json';
        state.config.url = url;
        updateIndicator(true);
        console.info('[MQTT realtime] WebSocket JSON conectado', url);
        resolve(socket);
      }, { once: true });

      socket.addEventListener('message', function(event) {
        try {
          const data = JSON.parse(String(event.data || ''));
          if (data && data.topic !== undefined) {
            handleMessage(String(data.topic), data.payload !== undefined ? data.payload : '');
          }
        } catch (err) {
          console.warn('[MQTT realtime] mensaje WebSocket JSON invalido', err);
        }
      });

      socket.addEventListener('close', function() {
        updateIndicator(false);
        if (!settled) fail(new Error('close antes de connect'));
      });

      socket.addEventListener('error', function() {
        if (!settled) fail(new Error('error de WebSocket'));
      });
    });
  }

  function onMessage(handler) {
    if (typeof handler === 'function') state.handlers.push(handler);
    connect().catch(function() {});
    return function unsubscribe() {
      state.handlers = state.handlers.filter(function(item) { return item !== handler; });
    };
  }

  function publish(topic, payload, options) {
    return connect().then(function(client) {
      return publishViaWebSocket(client, topic, payload, options).catch(function(err) {
        console.warn('[MQTT realtime] publish WebSocket fallo, probando backend', err && (err.message || err));
        return publishViaBackend(topic, payload, options);
      });
    });
  }

  function publishViaWebSocket(socket, topic, payload, options) {
    return new Promise(function(resolve, reject) {
      if (!socket || socket.readyState !== WebSocket.OPEN) {
        reject(new Error('WebSocket no conectado.'));
        return;
      }

      const publishOptions = options || {};
      const body = {
        action: 'publish',
        topic: withPrefix(topic),
        payload: typeof payload === 'string' ? payload : JSON.stringify(payload),
        qos: publishOptions.qos || 0,
        retain: !!publishOptions.retain
      };

      try {
        socket.send(JSON.stringify(body));
        resolve(body);
      } catch (err) {
        reject(err);
      }
    });
  }

  function publishViaBackend(topic, payload, options) {
    const publishOptions = options || {};
    return fetch('/api/mqtt_publish', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify({
        topic: withPrefix(topic),
        payload: payload,
        qos: publishOptions.qos || 0,
        retain: !!publishOptions.retain
      })
    }).then(function(response) {
      return response.json().then(function(data) {
        if (!response.ok || data.success === false) {
          throw new Error((data && data.message) || 'Fallo al publicar MQTT desde backend.');
        }
        return data;
      });
    });
  }

  global.mqttRealtime = {
    connect: connect,
    onMessage: onMessage,
    publish: publish,
    withPrefix: withPrefix,
    stripPrefix: stripPrefix
  };
})(window);
