// src/services/WebSocketManager.js

import store from "../store"; // Ensure the path is correct
import axios from "axios";
import {
  receiveMessage,
  receiveCallNotification,
  receiveSignalingMessage, // **Import the new action**
  receiveError,
  prependMessages,
  updateUserStatus,
  updateMessageStatus,
  endCall,
} from "../slices/notificationSlice";
import { generateRoomName } from "../utils"; // Ensure generateRoomName is correctly implemented

class WebSocketManager {
  constructor() {
    // Implement Singleton Pattern
    if (WebSocketManager.instance) {
      console.log(
        "WebSocketManager: Constructor called but instance already exists. Returning existing instance."
      );
      return WebSocketManager.instance;
    }

    console.log("WebSocketManager: Creating a new instance...");

    // Initialize properties
    this.socket = null;
    this.isConnected = false;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 3000; // 3 seconds
    this.messageQueue = [];
    this.token = null; // Store token for reconnection
    this.beforeMap = {}; // To track the 'before' timestamp per room
    this.hasMoreMap = {}; // To track if more messages are available per room
    this.messageHandlers = []; // Array of registered message handlers
    this.desiredRooms = new Set(); // Initialize without any rooms

    WebSocketManager.instance = this;
  }

  /**
   * Upload files via HTTP and return their URLs.
   * @param {File[]} files - Array of File objects to upload.
   * @returns {Promise<string[]>} - Array of uploaded file URLs.
   */
  async uploadFiles(files) {
    console.log("WebSocketManager: uploadFiles called with", files);
    const uploadUrl = "/api/upload/"; // Adjust to your upload endpoint
    const formData = new FormData();
    files.forEach((file) => formData.append("files", file));

    try {
      const response = await axios.post(uploadUrl, formData, {
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: `Bearer ${this.token}`,
        },
      });
      console.log(
        "WebSocketManager: uploadFiles success, server responded with:",
        response.data
      );
      return response.data.fileUrls; // Expecting { fileUrls: [url1, url2, ...] }
    } catch (error) {
      console.error("WebSocketManager: File upload failed:", error);
      throw error;
    }
  }

  /**
   * Fetch all chat rooms the user is part of by generating them based on appointments.
   * @returns {Promise<Array>} - Array of unique room objects with roomName property.
   */
  async fetchUserChatRooms() {
    console.log("WebSocketManager: fetchUserChatRooms() => Function started.");

    try {
      console.log("WebSocketManager: Retrieving application state from store.");
      const state = store.getState();

      // Determine if the user is a patient or a doctor
      let userType = null;
      let currentUserId = null;

      if (state.user?.userData?.patient?.user?.id) {
        userType = "patient";
        currentUserId = state.user.userData.patient.user.id;
        console.log("WebSocketManager: Determined user type as 'patient'.");
      } else if (state.doctor?.doctorData?.doctor?.user?.id) {
        userType = "doctor";
        currentUserId = state.doctor.doctorData.doctor.user.id;
        console.log("WebSocketManager: Determined user type as 'doctor'.");
      } else {
        console.log(
          "WebSocketManager: User type not found in store. Checking localStorage."
        );
        // Attempt to get from localStorage
        const storedUserData = JSON.parse(localStorage.getItem("userData"));
        const storedDoctorData = JSON.parse(localStorage.getItem("doctorData"));

        if (storedUserData?.patient?.user?.id) {
          userType = "patient";
          currentUserId = storedUserData.patient.user.id;
          console.log(
            "WebSocketManager: Determined user type as 'patient' from localStorage."
          );
        } else if (storedDoctorData?.doctor?.user?.id) {
          userType = "doctor";
          currentUserId = storedDoctorData.doctor.user.id;
          console.log(
            "WebSocketManager: Determined user type as 'doctor' from localStorage."
          );
        } else {
          console.error(
            "WebSocketManager: Unable to determine user type from state or localStorage."
          );
          throw new Error("Unable to determine user type.");
        }
      }

      // Ensure token is available
      if (!this.token) {
        console.error(
          "WebSocketManager: fetchUserChatRooms => Authentication token is not available."
        );
        throw new Error("Authentication token is not available.");
      }
      console.log(
        "WebSocketManager: fetchUserChatRooms => Authentication token is available."
      );

      let appointments = [];
      let endpoint = "";

      // Fetch appointments based on user type
      if (userType === "patient") {
        endpoint = "/api/get_patient_appointments/";
        console.log(
          `WebSocketManager: fetchUserChatRooms => Fetching patient appointments from '${endpoint}'.`
        );
        // Fetch patient appointments
        const response = await axios.get(endpoint, {
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
        });

        console.log(
          "WebSocketManager: fetchUserChatRooms => Patient appointments fetched. API Response:",
          response.data
        );

        if (response.data && Array.isArray(response.data)) {
          appointments = response.data;
          console.log(
            `WebSocketManager: Number of patient appointments retrieved: ${appointments.length}.`
          );
        } else {
          console.warn(
            "WebSocketManager: No appointments data found for patient."
          );
        }
      } else if (userType === "doctor") {
        endpoint = "/api/doctor/appointments/";
        console.log(
          `WebSocketManager: fetchUserChatRooms => Fetching doctor appointments from '${endpoint}'.`
        );
        // Fetch doctor appointments
        const response = await axios.get(endpoint, {
          headers: {
            Authorization: `Bearer ${this.token}`,
          },
        });

        console.log(
          "WebSocketManager: fetchUserChatRooms => Doctor appointments fetched. API Response:",
          response.data
        );

        if (response.data && Array.isArray(response.data)) {
          appointments = response.data;
          console.log(
            `WebSocketManager: Number of doctor appointments retrieved: ${appointments.length}.`
          );
        } else {
          console.warn(
            "WebSocketManager: No appointments data found for doctor."
          );
        }
      }

      // Check if any appointments were fetched
      if (appointments.length === 0) {
        console.warn(
          "WebSocketManager: No appointments available to generate chat rooms."
        );
        return [];
      }

      // Extract unique room names using generateRoomName
      const roomNameSet = new Set();
      console.log(
        "WebSocketManager: Processing appointments to generate room names."
      );

      appointments.forEach((appointment, index) => {
        console.log(
          `WebSocketManager: Processing appointment ${index + 1}/${
            appointments.length
          }: ID ${appointment.id}.`
        );

        const doctorId = appointment.doctor?.user?.id;
        const patientId = appointment.patient?.user?.id;

        if (doctorId && patientId) {
          const roomName = generateRoomName(doctorId, patientId);
          console.log(
            `WebSocketManager: Generated room name '${roomName}' for appointment ID ${appointment.id}.`
          );
          roomNameSet.add(roomName);
        } else {
          console.warn(
            `WebSocketManager: Missing doctor or patient ID in appointment ID ${appointment.id}. Skipping.`
          );
        }
      });

      console.log(
        "WebSocketManager: Unique room names extracted:",
        Array.from(roomNameSet)
      );

      // Create room objects
      const rooms = Array.from(roomNameSet).map((roomName) => ({ roomName }));
      console.log("WebSocketManager: Generated Chat Rooms:", rooms);

      console.log("WebSocketManager: fetchUserChatRooms => Completed successfully.");
      return rooms;
    } catch (error) {
      console.error(
        "WebSocketManager: fetchUserChatRooms => Error occurred while fetching user chat rooms:",
        error
      );
      // Dispatch a Redux action to notify the user if needed
      store.dispatch(
        receiveError({
          message: "Failed to fetch chat rooms based on appointments.",
          roomName: null,
        })
      );
      return [];
    }
  }

  /**
   * Establish WebSocket connection using the provided token.
   * @param {string} token - Authentication token.
   */
  async connect(token) {
    console.log("WebSocketManager: connect called with token:", token);

    if (
      this.socket &&
      (this.socket.readyState === WebSocket.OPEN ||
        this.socket.readyState === WebSocket.CONNECTING)
    ) {
      console.warn(
        "WebSocketManager: connect => WebSocket is already connected or connecting."
      );
      return;
    }

    if (!token) {
      console.error(
        "WebSocketManager: connect => Token is required to establish WebSocket connection."
      );
      return;
    }

    this.token = token; // Store token for reconnection

    const wsScheme = window.location.protocol === "https:" ? "wss" : "ws";
    const socketUrl = `${wsScheme}://${window.location.host}/ws/chat/?token=${encodeURIComponent(
      token
    )}`;

    console.log("WebSocketManager: connect => Attempting to open socket:", socketUrl);

    this.socket = new WebSocket(socketUrl);

    this.socket.onopen = async () => {
      console.log("WebSocketManager: socket.onopen event fired.");
      await this.handleSocketOpen();
    };

    this.socket.onmessage = (event) => {
      console.log("WebSocketManager: socket.onmessage => Raw event data:", event.data);
      try {
        const data = JSON.parse(event.data);
        console.log("WebSocketManager: Received WebSocket message:", data);

        // Handle specific message types
        switch (data.type) {
          case "chat_message":
          case "chat_message_event":
            this.handleChatMessage(data);
            break;
          case "previous_messages":
            this.handlePreviousMessages(data);
            break;
          case "call":
          case "call_message_event": // Handle normalized type
            this.handleCallNotification(data);
            break;
          case "offer":
          case "offer_event": // Handle normalized type
            this.handleSignalingMessage(data);
            break;
          case "answer":
          case "answer_event": // Handle normalized type
            this.handleSignalingMessage(data);
            break;
          case "ice-candidate":
          case "ice_candidate_event": // Handle normalized type
            this.handleSignalingMessage(data);
            break;
          case "user_status":
            this.handleUserStatus(data);
            break;
          case "message_status":
            this.handleMessageStatus(data);
            break;
          case "error":
            this.handleError(data);
            break;
          case "end-call":
          case "end_call_message_event": // Handle normalized type
            this.handleEndCall(data);
            break;
          default:
            console.warn("WebSocketManager: Unhandled message type:", data.type);
        }

        // Notify all registered handlers
        this.messageHandlers.forEach((handler) => handler(data));
      } catch (error) {
        console.error(
          "WebSocketManager: Error parsing WebSocket message:",
          error
        );
      }
    };

    this.socket.onclose = (event) => {
      console.log("WebSocketManager: socket.onclose event fired:", event);
      this.socket = null;
      this.isConnected = false;
      if (this.reconnectAttempts < this.maxReconnectAttempts) {
        this.reconnectAttempts++;
        const delay = this.reconnectDelay * this.reconnectAttempts;
        console.log(
          `WebSocketManager: Will attempt reconnect #${this.reconnectAttempts} in ${
            delay / 1000
          } seconds...`
        );
        setTimeout(() => this.connect(this.token), delay); // Use stored token
      } else {
        console.error(
          "WebSocketManager: Max reconnect attempts reached. Please refresh the page."
        );
        // Dispatch a Redux action to notify the user
        store.dispatch(
          receiveError({
            message: "Unable to connect to the server. Please refresh the page.",
            roomName: null,
          })
        );
      }
    };

    this.socket.onerror = (error) => {
      console.error("WebSocketManager: socket.onerror =>", error.message);
      this.safeClose();
      // Dispatch a Redux action to notify the user
      store.dispatch(
        receiveError({
          message: "WebSocket encountered an error. Connection closed.",
          roomName: null,
        })
      );
    };
  }

  /**
   * Handle WebSocket connection open event.
   * Subscribe to all user rooms.
   */
  async handleSocketOpen() {
    console.log("WebSocketManager: handleSocketOpen => Connection is open!");
    this.reconnectAttempts = 0;
    this.isConnected = true;

    // Fetch all chat rooms the user is part of
    console.log(
      "WebSocketManager: handleSocketOpen => Attempting to fetchUserChatRooms..."
    );
    const rooms = await this.fetchUserChatRooms();
    console.log(
      "WebSocketManager: handleSocketOpen => Rooms to subscribe to:",
      rooms
    );

    rooms.forEach((room) => {
      this.subscribe(room.roomName);
    });

    // Subscribe to presence updates or other necessary channels
    this.subscribeToUserStatus();

    // Send any queued messages
    console.log(
      `WebSocketManager: handleSocketOpen => Sending any queued messages. Queue length: ${this.messageQueue.length}`
    );
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.send(message);
    }
    console.log("WebSocketManager: handleSocketOpen => Completed initialization.");
  }

  /**
   * Subscribe to a specific room.
   * @param {string} roomName - The name of the room to subscribe to.
   */
  subscribe(roomName) {
    console.log(`WebSocketManager: subscribe => Called with roomName: ${roomName}`);
    if (this.desiredRooms.has(roomName)) {
      console.warn("WebSocketManager: Already subscribed to room:", roomName);
      return;
    }

    this.desiredRooms.add(roomName);
    const subscribeMessage = {
      type: "subscribe",
      room: roomName,
    };
    this.send(subscribeMessage);
    console.log("WebSocketManager: Subscribed to room:", roomName);

    // Fetch initial messages upon subscribing
    this.fetchPreviousMessages(roomName, 10);
  }

  /**
   * Unsubscribe from a specific room.
   * @param {string} roomName - The name of the room to unsubscribe from.
   */
  unsubscribe(roomName) {
    console.log(`WebSocketManager: unsubscribe => Called for roomName: ${roomName}`);
    if (!this.desiredRooms.has(roomName)) {
      console.warn("WebSocketManager: Not subscribed to room:", roomName);
      return;
    }

    const unsubscribeMessage = {
      type: "unsubscribe",
      room: roomName,
    };
    this.send(unsubscribeMessage);
    console.log("WebSocketManager: Unsubscribed from room:", roomName);

    this.desiredRooms.delete(roomName);

    // Clean up related state
    delete this.beforeMap[roomName];
    delete this.hasMoreMap[roomName];
  }

  /**
   * Register a message handler.
   * @param {function} handler - Function to handle incoming messages.
   */
  onMessage(handler) {
    console.log("WebSocketManager: onMessage => Adding a new message handler.");
    if (typeof handler === "function") {
      this.messageHandlers.push(handler);
    } else {
      console.error("WebSocketManager: Handler must be a function");
    }
  }

  /**
   * Unregister a message handler.
   * @param {function} handler - Function to remove from handlers.
   */
  offMessage(handler) {
    console.log("WebSocketManager: offMessage => Removing a message handler.");
    this.messageHandlers = this.messageHandlers.filter((h) => h !== handler);
  }

  /**
   * Fetch previous messages with pagination.
   * @param {string} roomName - The name of the room.
   * @param {number} limit - Number of messages to fetch.
   */
  fetchPreviousMessages(roomName, limit = 10) {
    console.log(
      `WebSocketManager: fetchPreviousMessages => Called for room: ${roomName}, limit: ${limit}`
    );
    if (this.hasMoreMap[roomName] === false) {
      console.log(
        "WebSocketManager: fetchPreviousMessages => No more messages to fetch for room:",
        roomName
      );
      return;
    }

    const before = this.beforeMap[roomName] || null;
    const fetchMessagePayload = {
      type: "fetch_previous_messages",
      roomName,
      limit,
    };
    if (before) {
      fetchMessagePayload.before = before;
    }
    console.log(
      "WebSocketManager: fetchPreviousMessages => Sending payload:",
      fetchMessagePayload
    );
    this.send(fetchMessagePayload);
  }

  /**
   * Send a signaling message to the server.
   * @param {number} toUserId - The ID of the user to send the signal to.
   * @param {string} signalType - The type of signal (e.g., 'offer', 'answer', 'ice-candidate').
   * @param {object} payload - Additional data for the signal.
   */
  sendSignalingMessage(toUserId, signalType, payload = {}) {
    const currentUserId = this.getCurrentUserId();
    console.log(
      `WebSocketManager: sendSignalingMessage => fromUserId=${currentUserId}, toUserId=${toUserId}, signalType=${signalType}, payload=`,
      payload
    );
    if (!currentUserId) {
      console.error("WebSocketManager: Cannot send signaling message without current user ID.");
      return;
    }

    const roomName = generateRoomName(currentUserId, toUserId);
    const message = {
      type: signalType,
      to: toUserId,
      roomName,
      ...payload,
    };
    this.send(message);
  }

  /**
   * Handle signaling messages like 'offer', 'answer', 'ice-candidate'.
   * @param {object} data - The signaling message data.
   */
  handleSignalingMessage(data) {
    const { type, from, fromFullName, roomName, ...rest } = data;
    console.log("WebSocketManager: handleSignalingMessage => Received data:", data);

    if (!from || !fromFullName || !roomName) {
      console.error(`WebSocketManager: handleSignalingMessage => Missing 'from', 'fromFullName', or 'roomName' in data.`);
      return;
    }

    console.log(
      `WebSocketManager: handleSignalingMessage => Received ${type} from ${fromFullName} (${from}) in room ${roomName}`,
      rest
    );

    // Dispatch appropriate Redux actions based on type
    switch (type) {
      case "offer":
      case "offer_event":
        // Handle offer
        store.dispatch(
          receiveSignalingMessage({
            signalType: "offer",
            fromId: from,
            roomName,
            offer: rest.offer, // Ensure 'offer' is present
          })
        );
        break;
      case "answer":
      case "answer_event":
        // Handle answer
        store.dispatch(
          receiveSignalingMessage({
            signalType: "answer",
            fromId: from,
            roomName,
            answer: rest.answer, // Ensure 'answer' is present
          })
        );
        break;
      case "ice-candidate":
      case "ice_candidate_event":
        // Handle ICE candidate
        store.dispatch(
          receiveSignalingMessage({
            signalType: "ice-candidate",
            fromId: from,
            roomName,
            candidate: rest.candidate, // Ensure 'candidate' is present
          })
        );
        break;
      default:
        console.warn("WebSocketManager: handleSignalingMessage => Unknown signal type:", type);
    }
  }

  /**
   * Handle incoming chat messages.
   * Dispatches receiveMessage action to Redux store with isNew=true.
   * @param {object} data - The message data.
   */
  handleChatMessage(data) {
    console.log("WebSocketManager: handleChatMessage => Received data:", data);
    const { roomName, sender, senderFullName, message, files, timestamp, id } = data;

    if (!roomName || !sender || !message || !id) {
      console.error("WebSocketManager: Invalid chat_message payload:", data);
      return;
    }

    const formattedFiles = Array.isArray(files)
      ? files.map((file) =>
          typeof file === "string" ? { name: file, url: file } : file
        )
      : [];

    store.dispatch(
      receiveMessage({
        roomName,
        id,
        sender,
        senderFullName,
        content: message,
        files: formattedFiles,
        timestamp: timestamp || new Date().toISOString(),
        isCurrentUser: sender === this.getCurrentUserId(),
        isNew: true, // Mark as new message
      })
    );
  }

  /**
   * Handle incoming call notifications.
   * Dispatches receiveCallNotification action to Redux store.
   * @param {object} data - The call notification data.
   */
  handleCallNotification(data) {
    console.log("WebSocketManager: handleCallNotification => Received data:", data);
    const { from, fromFullName, callType, roomName, patientId, doctorId } = data;

    if (!from || !fromFullName || !callType || !roomName) {
      console.error("WebSocketManager: Invalid call notification payload:", data);
      return;
    }

    // Prepare the payload with optional patientId or doctorId
    const callNotificationPayload = {
      fromId: from,
      fromFullName,
      callType,
      roomName,
    };

    if (patientId) {
      callNotificationPayload.patientId = patientId;
      console.log(`WebSocketManager: handleCallNotification => Added patientId: ${patientId}`);
    }

    if (doctorId) {
      callNotificationPayload.doctorId = doctorId;
      console.log(`WebSocketManager: handleCallNotification => Added doctorId: ${doctorId}`);
    }

    store.dispatch(receiveCallNotification(callNotificationPayload));
  }

  /**
   * Handle incoming previous messages.
   * Dispatches prependMessages action to Redux store with isNew=false.
   * @param {object} data - The previous messages data.
   */
  handlePreviousMessages(data) {
    console.log("WebSocketManager: handlePreviousMessages => Received data:", data);
    const { roomName, messages, hasMore } = data;

    if (!roomName || !Array.isArray(messages)) {
      console.warn(
        "WebSocketManager: Received previous_messages without a valid roomName or messages array."
      );
      return;
    }

    if (messages.length === 0) {
      console.log(
        "WebSocketManager: handlePreviousMessages => No previous messages received for room:",
        roomName
      );
      this.hasMoreMap[roomName] = false;
      return;
    }

    // Reverse messages to have oldest first
    const ascendingMessages = messages.reverse();

    const formattedMessages = ascendingMessages
      .map((msg) => {
        const {
          id,
          sender,
          senderFullName,
          message,
          files,
          timestamp,
          status,
        } = msg;

        if (!id || !sender || !message) {
          console.warn(
            "WebSocketManager: Invalid message format in previous_messages:",
            msg
          );
          return null;
        }

        const formattedFiles = Array.isArray(files)
          ? files.map((file) =>
              typeof file === "string" ? { name: file, url: file } : file
            )
          : [];

        return {
          id,
          roomName,
          sender,
          senderFullName,
          content: message,
          files: formattedFiles,
          timestamp: timestamp || new Date().toISOString(),
          status,
          isCurrentUser: sender === this.getCurrentUserId(),
          isNew: false,
        };
      })
      .filter((msg) => msg !== null);

    store.dispatch(
      prependMessages({
        roomName,
        messages: formattedMessages,
      })
    );

    // Set 'before' to the timestamp of the oldest message in the batch
    const earliestMessage = formattedMessages[0];
    if (earliestMessage) {
      this.beforeMap[roomName] = earliestMessage.timestamp;
    }

    // Update hasMoreMap based on hasMore flag from the server
    this.hasMoreMap[roomName] = hasMore;
    console.log(
      `WebSocketManager: handlePreviousMessages => Updated hasMoreMap[${roomName}] = ${hasMore}`
    );
  }

  /**
   * Handle incoming user status updates.
   * Dispatches updateUserStatus action to Redux store.
   * @param {object} data - The user status data.
   */
  handleUserStatus(data) {
    console.log("WebSocketManager: handleUserStatus => Received data:", data);
    const { userId, userFullName, status } = data;

    if (!userId || !userFullName || !status) {
      console.error("WebSocketManager: Invalid user_status payload:", data);
      return;
    }

    store.dispatch(
      updateUserStatus({
        userId,
        userFullName,
        status, // 'available', 'busy', or 'offline'
      })
    );
  }

  /**
   * Handle incoming 'end-call' messages.
   * Dispatches endCall action to Redux store.
   * @param {object} data - The 'end-call' message data.
   */
  handleEndCall(data) {
    console.log("WebSocketManager: handleEndCall => Received data:", data);
    const { from, fromFullName, roomName } = data;

    if (!from || !fromFullName || !roomName) {
      console.error(
        "WebSocketManager: handleEndCall => Missing 'from', 'fromFullName', or 'roomName'",
        data
      );
      return;
    }

    console.log(
      `WebSocketManager: handleEndCall => Call ended by user ID ${from} (${fromFullName}) in room ${roomName}`
    );

    store.dispatch(
      endCall({
        roomName,
        fromUserId: from,
        fromFullName,
      })
    );
  }

  /**
   * Handle incoming message status updates.
   * Dispatches updateMessageStatus action to Redux store.
   * @param {object} data - The message status data.
   */
  handleMessageStatus(data) {
    console.log("WebSocketManager: handleMessageStatus => Received data:", data);
    const { message_id, status } = data;

    if (!message_id || !status) {
      console.error("WebSocketManager: Invalid message_status payload:", data);
      return;
    }

    store.dispatch(
      updateMessageStatus({
        message_id,
        status,
      })
    );
  }

  /**
   * Handle error messages from the server.
   * Dispatches receiveError action to Redux store.
   * @param {object} data - The error data.
   */
  handleError(data) {
    console.log("WebSocketManager: handleError => Received error data:", data);
    const { message, roomName } = data;

    if (!message) {
      console.error("WebSocketManager: Invalid error payload:", data);
      return;
    }

    store.dispatch(
      receiveError({
        message,
        roomName: roomName || null,
      })
    );
  }

  /**
   * Helper method to get the current user's ID.
   * Retrieves from Redux store or localStorage.
   * @returns {number|null} - The current user's ID.
   */
  getCurrentUserId() {
    const state = store.getState();
    console.log("WebSocketManager: getCurrentUserId => Checking store and localStorage.");

    // Check if the user is a patient
    if (state.user?.userData?.patient?.user?.id) {
      return state.user.userData.patient.user.id;
    }

    // Check if the user is a doctor
    if (state.doctor?.doctorData?.doctor?.user?.id) {
      return state.doctor.doctorData.doctor.user.id;
    }

    // Try to get from localStorage
    const storedUserData = JSON.parse(localStorage.getItem("userData"));
    const storedDoctorData = JSON.parse(localStorage.getItem("doctorData"));

    if (storedUserData?.patient?.user?.id) {
      return storedUserData.patient.user.id;
    }

    if (storedDoctorData?.doctor?.user?.id) {
      return storedDoctorData.doctor.user.id;
    }

    console.warn("WebSocketManager: getCurrentUserId => Unable to determine current user ID.");
    return null;
  }

  /**
   * Subscribe to user status updates.
   */
  subscribeToUserStatus() {
    console.log("WebSocketManager: subscribeToUserStatus => Called.");
    const presenceRoom = "presence"; // You can keep this as a fixed room or update as needed
    if (!this.desiredRooms.has(presenceRoom)) {
      this.subscribe(presenceRoom);
      console.log("WebSocketManager: subscribeToUserStatus => Subscribed to presence room.");
    }
  }

  /**
   * Send presence status update.
   * @param {string} status - 'available', 'busy', or 'offline'.
   */
  sendPresenceStatus(status) {
    console.log("WebSocketManager: sendPresenceStatus => Called with status:", status);
    const presenceMessage = {
      type: "user_status",
      status, // 'available', 'busy', or 'offline'
    };
    this.send(presenceMessage);
  }

  /**
   * Send a read receipt for a specific message.
   * @param {number} toUserId - The ID of the user who sent the message.
   * @param {string} roomName - The name of the room.
   * @param {number} messageId - The ID of the message being read.
   */
  sendReadReceipt(toUserId, roomName, messageId) {
    console.log(
      `WebSocketManager: sendReadReceipt => toUserId=${toUserId}, roomName=${roomName}, messageId=${messageId}`
    );
    if (!toUserId || !roomName || !messageId) {
      console.error("WebSocketManager: sendReadReceipt => Missing toUserId, roomName, or messageId.");
      return;
    }

    const readReceipt = {
      type: "read_receipt",
      to: toUserId,
      roomName,
      message_id: messageId,
    };
    this.send(readReceipt);
  }

  /**
   * End a call with a specific user.
   * @param {number} toUserId - The ID of the user to end the call with.
   * @param {string} roomName - The name of the room.
   */
  endCall(toUserId, roomName) {
    console.log(
      `WebSocketManager: endCall => toUserId=${toUserId}, roomName=${roomName}`
    );
    if (!toUserId || !roomName) {
      console.error("WebSocketManager: endCall => Missing toUserId or roomName.");
      return;
    }

    const endCallPayload = {
      type: "end-call",
      to: toUserId,
      roomName,
    };

    this.send(endCallPayload);
    console.log(
      `WebSocketManager: endCall => Sent end-call message to user ID ${toUserId} in room ${roomName}.`
    );
  }

  /**
   * Send a message through the WebSocket connection.
   * If the connection isn't open, queue the message.
   * @param {object} data - The data to send.
   */
  send(data) {
    console.log("WebSocketManager: send => Attempting to send data:", data);
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      try {
        this.socket.send(JSON.stringify(data));
        console.log("WebSocketManager: Message sent successfully.");
      } catch (error) {
        console.error("WebSocketManager: Error sending message:", error);
        this.messageQueue.push(data); // Re-queue if sending fails
      }
    } else {
      console.warn(
        "WebSocketManager: send => WebSocket is not open. Queueing message:",
        data
      );
      this.messageQueue.push(data);
    }
  }
  

  /**
   * Safely close the WebSocket connection.
   */
  safeClose() {
    console.log("WebSocketManager: safeClose => Closing socket if not already closed.");
    if (this.socket && this.socket.readyState !== WebSocket.CLOSED) {
      this.socket.close();
      this.socket = null;
      this.isConnected = false;
    }
  }

  /**
   * Close the WebSocket connection and clear internal state.
   */
  close() {
    console.log("WebSocketManager: close => Clearing WebSocket state and internal data.");
    this.safeClose();
    this.desiredRooms.clear();
    this.messageQueue = [];
    this.token = null;
    this.reconnectAttempts = 0;
    this.messageHandlers = [];
    this.beforeMap = {};
    this.hasMoreMap = {};
  }

  /**
   * Get the current WebSocket connection state.
   * @returns {number|null} - The WebSocket readyState or null if not connected.
   */
  getState() {
    console.log(
      "WebSocketManager: getState => returning socket.readyState or null."
    );
    return this.socket ? this.socket.readyState : null;
  }

  /**
   * Send a chat message to a specific user.
   * @param {number} toUserId - The ID of the user to send the message to.
   * @param {string} message - The message content.
   * @param {File[]} files - Optional array of files to send.
   * @returns {Promise<void>}
   */
  async sendMessage(toUserId, message, files = []) {
    console.log(
      "WebSocketManager: sendMessage => Called with toUserId:",
      toUserId,
      "message:",
      message,
      "files:",
      files
    );
    if (!toUserId || !message) {
      console.error("WebSocketManager: sendMessage => Missing toUserId or message.");
      return;
    }

    // Upload files if any
    let fileUrls = [];
    if (files.length > 0) {
      try {
        fileUrls = await this.uploadFiles(files);
        console.log("WebSocketManager: sendMessage => fileUrls:", fileUrls);
      } catch (error) {
        console.error("WebSocketManager: Failed to upload files:", error);
        store.dispatch(
          receiveError({
            message: "Failed to upload files. Message not sent.",
            roomName: generateRoomName(this.getCurrentUserId(), toUserId),
          })
        );
        return;
      }
    }

    const roomName = generateRoomName(this.getCurrentUserId(), toUserId);
    console.log(
      `WebSocketManager: sendMessage => Sending chat_message to room: ${roomName}`
    );

    const messagePayload = {
      type: "chat_message",
      roomName,
      to: toUserId,
      message,
      files: fileUrls,
    };

    this.send(messagePayload);
  }

  /**
   * Initiate a call with a specific user.
   * @param {number} toUserId - The ID of the user to call.
   * @param {string} callType - 'audio' or 'video'.
   * @returns {Promise<void>}
   */
  async initiateCall(toUserId, callType = "audio") {
    console.log(
      `WebSocketManager: initiateCall => toUserId=${toUserId}, callType=${callType}`
    );
    if (!toUserId || !callType) {
      console.error("WebSocketManager: initiateCall => Missing toUserId or callType.");
      return;
    }

    const roomName = generateRoomName(this.getCurrentUserId(), toUserId);
    console.log(
      `WebSocketManager: initiateCall => Using roomName: ${roomName}`
    );

    const callPayload = {
      type: "call",
      roomName,
      to: toUserId,
      call_type: callType,
    };

    this.send(callPayload);
  }

  /**
   * Generate a unique ID for messages.
   * @returns {string} - A unique identifier.
   */
  generateUniqueId() {
    const uniqueId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    console.log("WebSocketManager: generateUniqueId => Generated:", uniqueId);
    return uniqueId;
  }
}

// Instantiate and export a single WebSocketManager instance
const webSocketManager = new WebSocketManager();
export default webSocketManager;