import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { generateUUID, getCookieValue, createCookie } from '../helpers/utils';
import { messageFactory, MessageType } from '../helpers/MessageFactory';
import { useJamClient } from '../context/JamClientContext';
import { useStun } from './useStun';

export const ConnectionStatus = {
  DISCONNECTED: 'disconnected',
  CONNECTING: 'connecting',
  CONNECTED: 'connected',
  RECONNECTING: 'reconnecting',
  ERROR: 'error'
};

export default function useJamServer(url) {
  const [connectionStatus, setConnectionStatus] = useState(ConnectionStatus.DISCONNECTED);
  const [reconnectAttempts, setReconnectAttempts] = useState(0);
  const [lastError, setLastError] = useState(null);
  const [hasReachedMaxAttempts, setHasReachedMaxAttempts] = useState(false);
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const ws = useRef(null);
  const callbacks = useRef({});
  const requestId = useRef(0);
  const reconnectTimeoutRef = useRef(null);
  const reconnectIntervalRef = useRef(null);
  const currentAttemptRef = useRef(0);
  const maxReconnectAttempts = 10;
  const baseReconnectDelay = 1000; // 1 second
  const maxReconnectDelay = 30000; // 30 seconds
  const shouldReconnect = useRef(true);
  const server = useRef({});
  const socketClosedListeners = useRef([]);

  // Heartbeat state
  const startHeartbeatTimeout = useRef(null);
  const heartbeatInterval = useRef(null);
  const heartbeatAckCheckInterval = useRef(null);
  const heartbeatMS = useRef(null);
  const connectionExpireTime = useRef(null);
  const lastHeartbeatSentTime = useRef(null);
  const lastHeartbeatAckTime = useRef(null);
  const lastHeartbeatFound = useRef(false);
  const notificationLastSeenAt = useRef(undefined);
  const notificationLastSeen = useRef(undefined);
  const userActive = useRef(true);
  const activityTimeout = useRef(null);

  const jamClient = useJamClient();
  const stun = useStun(server.current);

  // Calculate exponential backoff delay
  const getReconnectDelay = useCallback((attempt) => {
    const delay = Math.min(baseReconnectDelay * Math.pow(2, attempt), maxReconnectDelay);
    return delay + Math.random() * 1000; // Add jitter
  }, []);

  // Connect to WebSocket
  const connect = useCallback(async () => {
    if (ws.current && (ws.current.readyState === WebSocket.OPEN || ws.current.readyState === WebSocket.CONNECTING)) {
      return; // Already connected or connecting
    }

    setConnectionStatus(ConnectionStatus.CONNECTING);
    console.log('Attempting to connect to WebSocket...');

    try {
      const clientType = 'client'; //TODO: make dynamic
      //const clientId = await jamClient.clientID(); #This currently retuens empty string. need to add this as a method to c++ client. for now use hardcoded uuid
      const clientId = generateUUID(); //TODO: replace with jamClient.clientID() when available
      console.log("_DEBUG_ clientId", clientId);
      server.current.clientId = clientId;

      // Gather necessary parameters before establishing the connection
      // Use Promise.all to wait for all async calls to complete
      const operatingModePromise = await jamClient.getOperatingMode();
      console.log("_DEBUG_ operatingModePromise", operatingModePromise);
      const macHashPromise = await jamClient.SessionGetMacHash();
      const osStringPromise = await jamClient.GetDetailedOS();

      const allPromises = [
        operatingModePromise,
        macHashPromise,
        osStringPromise,
      ];

      Promise.all(allPromises).then(async ([operatingMode, machine, osString]) => {
        console.log("_DEBUG_ ", operatingMode, machine, osString)
        const channelId = generateUUID();
        const rememberToken = getCookieValue('remember_token');

        if (!rememberToken) {
          console.log("No remember_token found, user needs to log in.");
          //TODO: trigger login UI
        }

        machine = machine?.all || '';

        const params = {
          channel_id: channelId,
          token: rememberToken,
          client_type: clientType,
          client_id: clientId,
          machine: machine,
          os: osString,
          product: "JamClientModern",
          udp_reachable: await stun.sync(),
        }

        const queryString = new URLSearchParams(params)
        const fullUrl = `${url}?${queryString}`;

        ws.current = new WebSocket(fullUrl);

        ws.current.channelId = channelId;

        ws.current.onopen = () => {
          console.log('Connected to WebSocket server');
          setConnectionStatus(ConnectionStatus.CONNECTED);
          setReconnectAttempts(0);
          currentAttemptRef.current = 0;
          setLastError(null);
          setHasReachedMaxAttempts(false); // Reset the flag when we successfully connect

          // Re-initialize session after reconnection
          if (reconnectAttempts > 0) {
            console.log('Reconnected successfully, reinitializing session...');
            // You can add session recovery logic here
          }
          // If we have a remember token, attempt to log in automatically
          if (rememberToken) {
            rememberLogin(rememberToken, clientId);
          } else {
            //no remember token, show login screen
            console.log("No remember_token found, user needs to log in.");
            //TODO: trigger login UI
          }
        };

        ws.current.onmessage = function (e) {
          let message = JSON.parse(e.data),
            messageType = message.type.toLowerCase(),
            payload = message[messageType];
            console.log("Message callbacks message.type:", message.type);
          const cbks = callbacks.current[message.type];

          if (message.type === MessageType.PEER_MESSAGE) {
            //logger.info("server.onMessage:" + messageType);
            // route_to | from | peer_message
            try {
              console.log(`P2P ${message.from} > ${message.route_to} msg:${atob(atob(message.peer_message.message))}`)
            }
            catch (e) {
              console.log("P2P INVALID", JSON.parse(e.data));
            }
          }
          else if (message.type == MessageType.TRACKS_CHANGED) {
            console.log(`TRACKS_CHANGED counter:${message.tracks_changed.track_changes_counter}`)
          }
          else if (
            message.type !== MessageType.HEARTBEAT_ACK &&
            message.type !== MessageType.PEER_MESSAGE
          ) {
            console.log(
              "server.onMessage:" +
              messageType +
              " payload:" +
              JSON.stringify(payload)
            );
          }

          console.log(
              "server.onMessage:" +
              messageType +
              " payload:" +
              JSON.stringify(payload)
            );
          

          if (cbks !== undefined) {
            var len = cbks.length;
            for (var i = 0; i < len; i++) {
              try {
                if (typeof cbks[i] === 'function') {
                  cbks[i](message, payload);
                } else {
                  console.warn("callback at index", i, "for message type", message.type, "is not a function:", cbks[i]);
                }
              } catch (ex) {
                console.warn("exception in callback for websocket message:" + ex);
                throw ex;
              }
            }
          } else {
            console.info("Unexpected message type %s.", message.type);
          }
        };

        ws.current.onclose = (event) => {
          console.log('WebSocket disconnected:', event.code, event.reason);

          // Perform cleanup similar to original JamServer.js closedCleanup
          closedCleanup(!event.wasClean);

          // Attempt reconnection if it wasn't intentional and we haven't reached max attempts
          if (shouldReconnect.current && !event.wasClean && !hasReachedMaxAttempts) {
            scheduleReconnect();
          }
        };

        ws.current.onerror = (error) => {
          console.error('JamKazam WebSocket error:', error);
          setConnectionStatus(ConnectionStatus.ERROR);
          setLastError(error);

          // Attempt reconnection if we haven't reached max attempts
          if (shouldReconnect.current && !hasReachedMaxAttempts) {
            scheduleReconnect();
          }
        };
      })

    } catch (error) {
      console.error('_WEBSOCKET_ Failed to create WebSocket connection:', error);
      setConnectionStatus(ConnectionStatus.ERROR);
      setLastError(error);
      scheduleReconnect();
    }
  }, [url, reconnectAttempts]);

  const getClientId = (async () => {
    return getCookieValue('client_id') || await jamClient.clientID() || '';
  });

  const rememberLogin = useCallback((rememberToken, clientId) => {
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const loginMessage = messageFactory.loginWithToken(rememberToken, clientId, null, 'browser');
      ws.current.send(JSON.stringify(loginMessage));
      console.log('Sent remember login message', loginMessage);
    } else {
      console.warn('Cannot send remember login, WebSocket not connected');
    }
  }, []);

  // Schedule reconnection with exponential backoff
  const scheduleReconnect = useCallback(() => {
    if (!shouldReconnect.current) return;

    const attempt = currentAttemptRef.current;
    if (attempt >= maxReconnectAttempts) {
      console.log(`Max reconnection attempts reached (${attempt})`);
      setConnectionStatus(ConnectionStatus.ERROR);
      setHasReachedMaxAttempts(true);
      return;
    }

    const delay = getReconnectDelay(attempt);
    console.log(`Scheduling reconnection in ${delay}ms (attempt ${attempt + 1}/${maxReconnectAttempts})`);

    setConnectionStatus(ConnectionStatus.RECONNECTING);
    const newAttempt = attempt + 1;
    setReconnectAttempts(newAttempt);
    currentAttemptRef.current = newAttempt;

    // Clear any existing timeout to prevent overlapping reconnect attempts
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
    }

    reconnectTimeoutRef.current = setTimeout(() => {
      connect();
    }, delay);
  }, [maxReconnectAttempts, getReconnectDelay, connect]);

  // Disconnect WebSocket
  const disconnect = useCallback(() => {
    shouldReconnect.current = false;

    // Stop heartbeat timers
    stopHeartbeat();

    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }

    if (ws.current) {
      ws.current.close(1000, 'Client disconnecting');
    }

    setConnectionStatus(ConnectionStatus.DISCONNECTED);
  }, []);

  const sendMessage = useCallback((type, params = {}) => {
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const message = {
        type,
        params,
        id: ++requestId.current
      };
      ws.current.send(JSON.stringify(message));
      return message.id;
    }
    return null;
  }, []);

  const sendRequest = useCallback((type, params = {}) => {
    return new Promise((resolve, reject) => {
      const id = sendMessage(type, params);
      if (id) {
        const timeout = setTimeout(() => {
          reject(new Error('Request timeout'));
        }, 5000);

        const responseHandler = (event) => {
          try {
            const data = JSON.parse(event.data);
            if (data.id === id) {
              clearTimeout(timeout);
              ws.current.removeEventListener('message', responseHandler);
              resolve(data);
            }
          } catch (error) {
            console.error('Error parsing response:', error);
          }
        };

        ws.current.addEventListener('message', responseHandler);
      } else {
        reject(new Error('WebSocket not connected'));
      }
    });
  }, [sendMessage]);


  const registerMessageCallback = useCallback((messageType, callback) => {
    if (!callbacks.current[messageType]) {
      callbacks.current[messageType] = []
    }
    if (typeof callback === 'function') {
      callbacks.current[messageType].push(callback);
    } else {
      console.warn('registerMessageCallback: callback is not a function for messageType:', messageType, callback);
    }
  });

  const unregisterMessageCallback = useCallback((messageType, callback) => {
    if (callbacks.current[messageType]) {
      // Remove specific callback from array
      callbacks.current[messageType] = callbacks.current[messageType].filter(cb => cb !== callback);
    }
    // If no callbacks remain for this type, delete the array
    if (callbacks.current[messageType] && callbacks.current[messageType].length === 0) {
      delete callbacks.current[messageType];
    }

  })

  //user logged in to session screen. register login callback with the server
  useEffect(() => {
    registerLoginAck()
    registerServerRejection()
    registerHeartbeatAck()
    registerSocketClosed()
  }, [])

  const registerLoginAck = useCallback(() => {
    // Register the login acknowledgment callback
    console.log("registering login ack callback")
    registerMessageCallback(MessageType.LOGIN_ACK, loggedIn)
  }, []);

  const registerHeartbeatAck = useCallback(() => {
    // Register the login acknowledgment callback
    console.log("registering heartbeat ack callback")
    registerMessageCallback(MessageType.HEARTBEAT_ACK, heartbeatAck)
  }, []);

  const registerServerRejection = useCallback(() => {
    // Register the login acknowledgment callback
    console.log("registering server rejection callback")
    registerMessageCallback(MessageType.SERVER_REJECTION, serverRejection)
  }, []);

  // Register socket closed callback (equivalent to original registerSocketClosed)
  const registerSocketClosed = useCallback(() => {
    console.log("registering socket closed callback");
    registerOnSocketClosed(socketClosed);
  }, []);

  // Register a callback for socket closed events
  const registerOnSocketClosed = useCallback((callback) => {
    socketClosedListeners.current.push(callback);
  }, []);

  // Socket closed callback function
  const socketClosed = useCallback((inError) => {
    console.log("Socket closed callback called", inError ? "with error" : "cleanly");
    // This callback can be used by external components to react to socket closure
  }, []);

  // Heartbeat state reset
  const heartbeatStateReset = useCallback(() => {
    lastHeartbeatSentTime.current = null;
    lastHeartbeatAckTime.current = null;
    lastHeartbeatFound.current = false;
  }, []);

  // Check for heartbeat ack timeout
  const heartbeatAckCheck = useCallback(() => {
    // If we've seen an ack to the latest heartbeat, don't bother with checking again
    if (lastHeartbeatFound.current) {
      return;
    }

    // Check if the server is still sending heartbeat acks back
    if (lastHeartbeatAckTime.current && new Date().getTime() - lastHeartbeatAckTime.current.getTime() > connectionExpireTime.current) {
      console.error("no heartbeat ack received from server after", connectionExpireTime.current, "seconds. giving up on socket connection");
      // Close connection due to heartbeat failure
      if (ws.current) {
        ws.current.close(1000, 'NO_HEARTBEAT_ACK');
      }
    } else {
      lastHeartbeatFound.current = true;
    }
  }, []);

  // Send heartbeat message
  const sendHeartbeat = useCallback(() => {
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const message = messageFactory.heartbeat(
        notificationLastSeen.current,
        notificationLastSeenAt.current,
        userActive.current
      );
      notificationLastSeenAt.current = undefined;
      notificationLastSeen.current = undefined;

      // Check for significant drift between heartbeats
      const now = new Date();
      if (lastHeartbeatSentTime.current) {
        const drift = new Date().getTime() - lastHeartbeatSentTime.current.getTime() - heartbeatMS.current;
        if (drift > 500) {
          console.warn("significant drift between heartbeats:", drift, 'ms beyond target interval');
        }
      }

      lastHeartbeatSentTime.current = now;
      ws.current.send(JSON.stringify(message));
      lastHeartbeatFound.current = false;
    }
  }, []);

  // Handle heartbeat ack from server
  const heartbeatAck = useCallback((header, payload) => {
    lastHeartbeatAckTime.current = new Date();
  }, []);

  // Start heartbeat after login
  const startHeartbeat = useCallback((payload) => {
    heartbeatMS.current = payload.heartbeat_interval * 1000;
    connectionExpireTime.current = payload.connection_expire_time * 1000;
    console.info("loggedIn(): heartbeat=" + payload.heartbeat_interval + "s expire_time=" + payload.connection_expire_time + 's');

    heartbeatStateReset();

    // Clear any existing heartbeat timers
    if (startHeartbeatTimeout.current) {
      clearTimeout(startHeartbeatTimeout.current);
      startHeartbeatTimeout.current = null;
    }
    if (heartbeatInterval.current) {
      clearInterval(heartbeatInterval.current);
      heartbeatInterval.current = null;
    }
    if (heartbeatAckCheckInterval.current) {
      clearInterval(heartbeatAckCheckInterval.current);
      heartbeatAckCheckInterval.current = null;
    }

    // Add some randomness to help move heartbeats apart from each other
    // Send 1st heartbeat somewhere between 0 - 0.5 of the connection expire time
    const randomStartTime = connectionExpireTime.current * (Math.random() / 2);

    startHeartbeatTimeout.current = setTimeout(() => {
      if (ws.current && ws.current.readyState === WebSocket.OPEN) {
        heartbeatInterval.current = setInterval(sendHeartbeat, heartbeatMS.current);
        heartbeatAckCheckInterval.current = setInterval(heartbeatAckCheck, 1000);
        // Add a little forgiveness to server for initial heartbeat
        lastHeartbeatAckTime.current = new Date(new Date().getTime() + heartbeatMS.current);
      }
    }, randomStartTime);

    console.info("starting heartbeat timer in", randomStartTime / 1000, 's');
  }, [heartbeatStateReset, sendHeartbeat, heartbeatAckCheck]);

  // Stop heartbeat timers
  const stopHeartbeat = useCallback(() => {
    if (heartbeatInterval.current) {
      clearInterval(heartbeatInterval.current);
      heartbeatInterval.current = null;
    }
    if (startHeartbeatTimeout.current) {
      clearTimeout(startHeartbeatTimeout.current);
      startHeartbeatTimeout.current = null;
    }
    if (heartbeatAckCheckInterval.current) {
      clearInterval(heartbeatAckCheckInterval.current);
      heartbeatAckCheckInterval.current = null;
    }
  }, []);

  // Cleanup when WebSocket connection closes
  const closedCleanup = useCallback((inError) => {
    console.log('WebSocket connection closed, cleaning up...', inError ? 'with error' : 'cleanly');

    // Update connection state
    if (connectionStatus === ConnectionStatus.CONNECTED) {
      setConnectionStatus(ConnectionStatus.DISCONNECTED);
      // TODO: Trigger CONNECTION_DOWN event if needed
    }

    // Reset server state
    //server.current.connected = false;
    //server.current.connecting = false;

    // Stop all heartbeat-related timers
    stopHeartbeat();

    // Clear connection timeout
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }

    // Notify all registered socket closed listeners
    const listeners = socketClosedListeners.current;
    const len = listeners.length;
    for (let i = 0; i < len; i++) {
      try {
        listeners[i](inError);
      } catch (ex) {
        console.warn('exception in callback for websocket closed event:', ex);
      }
    }

    // Handle reconnection logic
    // In React hook, we rely on the existing reconnection system
    // The onclose handler will trigger reconnection if appropriate
  }, [connectionStatus, stopHeartbeat]);

  // Update notification seen
  const updateNotificationSeen = useCallback((notificationId, notificationCreatedAt) => {
    const time = new Date(notificationCreatedAt);

    if (!notificationCreatedAt) {
      throw new Error('invalid value passed to updateNotificationSeen');
    }

    if (!notificationLastSeenAt.current) {
      notificationLastSeenAt.current = notificationCreatedAt;
      notificationLastSeen.current = notificationId;
      console.debug("updated notificationLastSeenAt with:", notificationCreatedAt);
    } else if (time.getTime() > new Date(notificationLastSeenAt.current).getTime()) {
      notificationLastSeenAt.current = notificationCreatedAt;
      notificationLastSeen.current = notificationId;
      console.debug("updated notificationLastSeenAt with:", notificationCreatedAt);
    } else {
      console.debug("ignored notificationLastSeenAt for:", notificationCreatedAt);
    }
  }, []);

  // Set user active state (similar to original setActive function)
  const setActive = useCallback((active) => {
    // In React hook context, we could trigger callbacks or update context here
    // For now, we'll just log the state change
    console.debug("User activity state changed:", active);
  }, []);

  // Mark user as away (inactive)
  const markAway = useCallback(() => {
    console.debug("sleep again!");
    userActive.current = false;
    setActive(userActive.current);
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const userStatus = messageFactory.userStatus(false, null);
      ws.current.send(JSON.stringify(userStatus));
    }
  }, [setActive]);

  // Mark user as active
  const markActive = useCallback(() => {
    if (activityTimeout.current) {
      clearTimeout(activityTimeout.current);
      activityTimeout.current = null;
    }

    if (!userActive.current) {
      if (ws.current && ws.current.readyState === WebSocket.OPEN) {
        console.debug("awake again!");
        const userStatus = messageFactory.userStatus(true, null);
        ws.current.send(JSON.stringify(userStatus));
      }
    }
    userActive.current = true;
    setActive(userActive.current);
    const timeoutTime = 300000; // 5 minutes
    activityTimeout.current = setTimeout(markAway, timeoutTime);
  }, [markAway, setActive]);

  // Initialize activity checking (equivalent to original activityCheck function)
  const activityCheck = useCallback(() => {
    const timeoutTime = 300000; // 5 * 1000 * 60, 5 minutes
    userActive.current = true;
    setActive(userActive.current);
    activityTimeout.current = setTimeout(markAway, timeoutTime);

    // Activity event handler
    const handleActivity = () => {
      if (activityTimeout.current) {
        clearTimeout(activityTimeout.current);
        activityTimeout.current = null;
      }

      if (!userActive.current) {
        if (ws.current && ws.current.readyState === WebSocket.OPEN) {
          console.debug("awake again!");
          const userStatus = messageFactory.userStatus(true, null);
          ws.current.send(JSON.stringify(userStatus));
        }
      }
      userActive.current = true;
      setActive(userActive.current);
      activityTimeout.current = setTimeout(markAway, timeoutTime);
    };

    // Bind activity events to document
    document.addEventListener('mousedown', handleActivity);
    document.addEventListener('keydown', handleActivity);
    document.addEventListener('touchstart', handleActivity);
    document.addEventListener('focus', handleActivity);

    // Return cleanup function
    return () => {
      document.removeEventListener('mousedown', handleActivity);
      document.removeEventListener('keydown', handleActivity);
      document.removeEventListener('touchstart', handleActivity);
      document.removeEventListener('focus', handleActivity);
      if (activityTimeout.current) {
        clearTimeout(activityTimeout.current);
      }
    };
  }, [markAway, setActive]);

  // Activity monitoring (equivalent to original activityCheck)
  useEffect(() => {
    const cleanupActivityCheck = activityCheck();

    // Cleanup function returned by activityCheck
    return cleanupActivityCheck;
  }, [activityCheck]);

  const loggedIn = useCallback((header, payload) => {
    console.log("logged in callback from websocket", header, payload);
    server.current.ws = ws.current;
    server.current.signedIn = true;
    server.current.clientId = payload.client_id;
    server.current.publicIp = payload.public_ip;

    jamClient.clientID = payload.client_id;

    heartbeatStateReset();

    // Start heartbeat after successful login
    startHeartbeat(payload);

    try {
      const msg = {
        user_id: payload.user_id,
        token: payload.token,
        username: payload.username,
        arses: payload.arses,
        client_id_int: payload.client_id_int,
        client_id: payload.client_id,
        subscription: payload.subscription
      }
      if (payload.connection_policy) {
        try {
          msg.policy = JSON.parse(payload.connection_policy)
        }
        catch (e) {
          msg.policy = null
          console.log("unable to parse connection policy", e)
        }
      }
      console.log("logged with new msg", msg)
      jamClient.OnLoggedIn(msg); // ACTS AS CONTINUATION
    } catch (error) {
      console.log("fallback to old callback", error)
      jamClient.OnLoggedIn(payload.user_id, payload.token, payload.username); // ACTS AS CONTINUATION
    }
    setIsLoggedIn(true);

    // set clientId cookie if not already set
    if (!getCookieValue('client_id')) {
      createCookie('client_id', payload.client_id, 365);
    }
  }, [heartbeatStateReset, startHeartbeat]);

  const serverRejection = ((header, payload) => {
    console.log("server rejection callback from websocket", header, payload);
    alert("Server rejected connection: " + payload.reason);
    jamClient.OnServerRejection(payload.reason);
  });

  //Initialize connection
  useEffect(() => {
    connect();

    return () => {
      disconnect();
    };
  }, []);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      shouldReconnect.current = false;
      // Stop heartbeat timers
      stopHeartbeat();
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
      if (ws.current) {
        ws.current.close();
      }
    };
  }, []);

  const isConnected = useMemo(() => {
    return connectionStatus === ConnectionStatus.CONNECTED;
  }, [connectionStatus]);

  // Subscribe to WebSocket notifications for a specific resource
  const subscribe = useCallback((type, id) => {
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const subscribeMessage = messageFactory.subscribe(type, id.toString());
      ws.current.send(JSON.stringify(subscribeMessage));
      console.log(`[WebSocket] Subscribed to ${type}:${id}`);
      return true;
    } else {
      console.warn(`[WebSocket] Cannot subscribe, not connected`);
      return false;
    }
  }, []);

  // Unsubscribe from WebSocket notifications for a specific resource
  const unsubscribe = useCallback((type, id) => {
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      const unsubscribeMessage = messageFactory.unsubscribe(type, id.toString());
      ws.current.send(JSON.stringify(unsubscribeMessage));
      console.log(`[WebSocket] Unsubscribed from ${type}:${id}`);
      return true;
    } else {
      console.warn(`[WebSocket] Cannot unsubscribe, not connected`);
      return false;
    }
  }, []);

  return {
    // Legacy properties for backward compatibility
    isConnected,

    // New connection management properties
    connectionStatus,
    reconnectAttempts,
    lastError,

    // Methods
    jamClient,
    sendMessage,
    sendRequest,
    connect,
    disconnect,
    updateNotificationSeen,
    registerOnSocketClosed,
    subscribe,
    unsubscribe,

    isLoggedIn,

    server: server.current,

    // Callbacks management
    registerMessageCallback,
    unregisterMessageCallback,
  };
}
