/**
 * ZeqText Client — Messaging API + WebSocket
 * Ported from apps/zeq-encrypted-text/ client-side logic.
 * Connects to ZeqText server on port 3009.
 *
 * @version 1.287.3
 * @license CC BY 4.0 — Zeq. H / Zeq 1.287 Hz Limited
 */
const ZeqTextClient = (() => {
  'use strict';

  const API_BASE = 'http://localhost/apps/zeqtext';
  const WS_URL   = 'ws://localhost/apps/zeqtext/ws';
  const AI_BOT_ZID = 'ZEQ07000000001';

  let ws = null;
  let wsReady = false;
  let reconnectAttempts = 0;
  let reconnectTimer = null;
  let token = null;
  let myZid = null;

  // Callbacks
  const listeners = {
    message: [],
    messageSent: [],
    presence: [],
    typing: [],
    readReceipt: [],
    status: [],
    contactsChanged: [],
  };

  // ── Connection ──────────────────────────────────────────────────────────
  function connect(authToken, zid) {
    token = authToken;
    myZid = zid;
    if (ws) { try { ws.close(); } catch {} }

    _notifyStatus('connecting');
    ws = new WebSocket(WS_URL);

    ws.onopen = () => {
      reconnectAttempts = 0;
      ws.send(JSON.stringify({ type: 'auth', token }));
    };

    ws.onmessage = (event) => {
      let data;
      try { data = JSON.parse(event.data); } catch { return; }
      _handleMessage(data);
    };

    ws.onclose = () => {
      wsReady = false;
      _notifyStatus('disconnected');
      const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
      reconnectAttempts++;
      reconnectTimer = setTimeout(() => {
        if (token) connect(token, myZid);
      }, delay);
    };

    ws.onerror = () => {};
  }

  function disconnect() {
    if (reconnectTimer) clearTimeout(reconnectTimer);
    reconnectTimer = null;
    token = null;
    myZid = null;
    if (ws) { try { ws.close(); } catch {} }
    ws = null;
    wsReady = false;
    _notifyStatus('disconnected');
  }

  function isConnected() { return wsReady; }
  function getMyZid()    { return myZid; }

  // ── WebSocket send ──────────────────────────────────────────────────────
  function wsSend(data) {
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(data));
    }
  }

  // ── Handle WS messages ──────────────────────────────────────────────────
  function _handleMessage(data) {
    switch (data.type) {
      case 'auth_success':
        wsReady = true;
        _notifyStatus('connected');
        break;
      case 'message':
        _notify('message', data.message);
        break;
      case 'message_sent':
        _notify('messageSent', data.message);
        break;
      case 'typing':
        _notify('typing', { senderZid: data.senderZid, isTyping: data.isTyping });
        break;
      case 'read_receipt':
        _notify('readReceipt', { messageId: data.messageId, readerZid: data.readerZid });
        break;
      case 'presence':
        _notify('presence', { zid: data.zid, online: data.online });
        break;
      case 'error':
        console.warn('[ZeqText]', data.message);
        break;
    }
  }

  // ── REST API — Contacts ─────────────────────────────────────────────────
  async function getContacts() {
    const res = await fetch(`${API_BASE}/api/contacts`, {
      headers: { 'Authorization': `Bearer ${token}` },
    });
    if (!res.ok) throw new Error(`Contacts: ${res.status}`);
    return res.json();
  }

  async function addContact(zidOrName) {
    const body = {};
    if (zidOrName.startsWith('ZEQ-')) {
      body.zid = zidOrName;
    } else {
      body.displayName = zidOrName;
    }
    const res = await fetch(`${API_BASE}/api/contacts/add`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.error || `Add contact: ${res.status}`);
    }
    _notify('contactsChanged');
    return res.json();
  }

  async function searchUsers(query) {
    const res = await fetch(`${API_BASE}/api/users/search?q=${encodeURIComponent(query)}`, {
      headers: { 'Authorization': `Bearer ${token}` },
    });
    if (!res.ok) return [];
    return res.json();
  }

  async function removeContact(contactZid) {
    const res = await fetch(`${API_BASE}/api/contacts/${contactZid}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${token}` },
    });
    if (!res.ok) throw new Error(`Remove contact: ${res.status}`);
    _notify('contactsChanged');
  }

  // ── REST API — Messages ─────────────────────────────────────────────────
  async function getMessages(contactZid, limit = 100) {
    const res = await fetch(`${API_BASE}/api/messages/${contactZid}?limit=${limit}`, {
      headers: { 'Authorization': `Bearer ${token}` },
    });
    if (!res.ok) return [];
    return res.json();
  }

  // ── Send message via WebSocket ──────────────────────────────────────────
  function sendMessage(recipientZid, content) {
    const messageId = crypto.randomUUID
      ? crypto.randomUUID()
      : 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
          const r = Math.random() * 16 | 0;
          return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
        });

    wsSend({
      type: 'message',
      recipientZid,
      content,
      messageId,
    });

    return messageId;
  }

  // ── Typing indicator ────────────────────────────────────────────────────
  function sendTyping(recipientZid, isTyping) {
    wsSend({ type: 'typing', recipientZid, isTyping });
  }

  // ── Read receipt ────────────────────────────────────────────────────────
  function sendReadReceipt(messageId, senderZid) {
    wsSend({ type: 'read_receipt', messageId, senderZid });
  }

  // ── Event system ────────────────────────────────────────────────────────
  function on(event, callback) {
    if (listeners[event]) listeners[event].push(callback);
  }

  function off(event, callback) {
    if (listeners[event]) {
      listeners[event] = listeners[event].filter(cb => cb !== callback);
    }
  }

  function _notify(event, data) {
    if (listeners[event]) {
      listeners[event].forEach(cb => {
        try { cb(data); } catch (e) { console.error('[ZeqText] listener error:', e); }
      });
    }
  }

  function _notifyStatus(status) {
    _notify('status', status);
  }

  // ── Register on ZeqText + auto-connect ──────────────────────────────
  async function registerAndConnect(displayName, equation) {
    const res = await fetch(`${API_BASE}/api/register`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ displayName, equation }),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.error || `ZeqText register: ${res.status}`);
    }
    const data = await res.json();
    connect(data.token, data.zid);
    return data;
  }

  // ── Login on ZeqText + auto-connect ───────────────────────────────────
  // If not registered, silently tries to register as fallback
  async function loginAndConnect(equation, displayName) {
    try {
      const res = await fetch(`${API_BASE}/api/login`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ equation }),
      });
      if (res.ok) {
        const data = await res.json();
        connect(data.token, data.zid);
        return data;
      }
      // If login fails (not registered), try registering
      if (displayName) {
        return await registerAndConnect(displayName, equation);
      }
      throw new Error('ZeqText login failed and no displayName for auto-register');
    } catch (err) {
      // If network error and we have a displayName, try register
      if (displayName) {
        try { return await registerAndConnect(displayName, equation); }
        catch { /* fall through */ }
      }
      throw err;
    }
  }

  return {
    connect,
    disconnect,
    isConnected,
    getMyZid,
    getContacts,
    addContact,
    searchUsers,
    removeContact,
    getMessages,
    sendMessage,
    sendTyping,
    sendReadReceipt,
    registerAndConnect,
    loginAndConnect,
    on,
    off,
    AI_BOT_ZID,
  };
})();
