// jam-ui/src/components/client/JKSessionScreen.js
import React, { useEffect, useRef, useState, memo, useMemo, useCallback } from 'react'
import { useParams, useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';

import useGearUtils from '../../hooks/useGearUtils'
import useSessionUtils from '../../hooks/useSessionUtils.js';
import useSessionModel from '../../hooks/useSessionModel.js';
import useSessionHelper from '../../hooks/useSessionHelper.js';
import useRecordingHelpers from '../../hooks/useRecordingHelpers.js';
import useMixerStore from '../../hooks/useMixerStore.js';

import { useJamServerContext } from '../../context/JamServerContext.js';
import { useGlobalContext } from '../../context/GlobalContext.js';
import { useJamKazamApp } from '../../context/JamKazamAppContext.js';
import { useMixersContext } from '../../context/MixersContext.js';
import { useAuth } from '../../context/UserAuth';
import useMediaActions from '../../hooks/useMediaActions';

import { dkeys } from '../../helpers/utils.js';

import { getSessionHistory, getSession, joinSession as joinSessionRest, updateSessionSettings, getFriends, startRecording, stopRecording, submitSessionFeedback, getVideoConferencingRoomUrl, getJamTrack, closeJamTrack, openMetronome } from '../../helpers/rest';
import { syncTracksToServer } from '../../services/trackSyncService';

// Redux imports
import { openModal, closeModal, toggleModal, selectModal, selectIsRecording, setRecordingStarted, setRecordingStopped } from '../../store/features/sessionUISlice';
import { selectMediaSummary, selectMetronomeTrackMixers, selectMixersReady } from '../../store/features/mixersSlice';
import {
  fetchActiveSession,
  joinActiveSession,
  leaveActiveSession,
  setGuardsPassed,
  setUserTracks,
  setConnectionStatus,
  setSessionId,
  updateSessionData,
  setSelectedJamTrack,
  setJamTrackStems,
  setBackingTrackData,
  clearBackingTrackData,
  setJamTrackData,
  clearJamTrackData,
  clearSession,
  selectActiveSession,
  selectJoinStatus,
  selectHasJoined,
  selectGuardsPassed,
  selectUserTracks,
  selectShowConnectionAlert,
  selectSessionId,
  selectInSession,
  selectSelectedJamTrack,
  selectJamTrackStems,
  selectBackingTrackData,
  selectJamTrackData
} from '../../store/features/activeSessionSlice';
import { addMessageFromWebSocket, uploadAttachment, selectIsUploading, selectUploadError, selectUploadFileName, selectUploadStatus, clearUploadError, fetchChatHistory, clearAllMessages } from '../../store/features/sessionChatSlice';
import { validateFile } from '../../services/attachmentValidation';
import { checkJamTrackSync } from '../../store/features/mediaSlice';

import { CLIENT_ROLE, RECORD_TYPE_AUDIO, RECORD_TYPE_BOTH } from '../../helpers/globals';
import { MessageType } from '../../helpers/MessageFactory.js';

import { Alert, Col, Row, Button, Card, CardBody, Modal, ModalHeader, ModalBody, ModalFooter, CardHeader, Badge, UncontrolledTooltip, Spinner } from 'reactstrap';
import FalconCardHeader from '../common/FalconCardHeader';
//import SessionTrackVU from './SessionTrackVU.js';
//import JKSessionMyTrack from './JKSessionMyTrack.js';
import JKSessionAudioInputs from './JKSessionAudioInputs.js';
import JKSessionRemoteTracks from './JKSessionRemoteTracks.js';
import JKSessionSettingsModal from './JKSessionSettingsModal.js';
import JKSessionInviteModal from './JKSessionInviteModal.js';
import JKSessionVolumeModal from './JKSessionVolumeModal.js';
import JKSessionRecordingModal from './JKSessionRecordingModal.js';
import JKSessionLeaveModal from './JKSessionLeaveModal.js';
import JKSessionJamTrackModal from './JKSessionJamTrackModal.js';
import JKSessionJamTrackStems from './JKSessionJamTrackStems.js';
import JKSessionOpenMenu from './JKSessionOpenMenu.js';
import WindowPortal from '../common/WindowPortal.js';
import JKSessionBackingTrackPlayer from './JKSessionBackingTrackPlayer.js';
import JKSessionJamTrackPlayer from './JKSessionJamTrackPlayer.js';
import JKSessionBackingTrack from './JKSessionBackingTrack.js';
import JKSessionMetronome from './JKSessionMetronome.js';
import JKSessionMetronomePlayer from './JKSessionMetronomePlayer.js';
import JKSessionChatWindow from './JKSessionChatWindow.js';
import JKSessionChatButton from './JKSessionChatButton.js';
import JKPopupMediaControls from '../popups/JKPopupMediaControls.js';
import { SESSION_PRIVACY_MAP } from '../../helpers/globals.js';
import { toast } from 'react-toastify';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

//icon imports
import gearIcon from '../../assets/img/client/gear.svg';
import inviteIcon from '../../assets/img/client/invite.svg';
import volumeIcon from '../../assets/img/client/volume.svg';
import videoIcon from '../../assets/img/client/video.svg';
import recordIcon from '../../assets/img/client/record.svg';
import broadcastIcon from '../../assets/img/client/broadcast.svg';
import openIcon from '../../assets/img/client/open.svg';
import chatIcon from '../../assets/img/client/chat.svg';
import attachIcon from '../../assets/img/client/attach.svg';
import resyncIcon from '../../assets/img/client/resync.svg';
import helpIcon from '../../assets/img/client/help.svg';

const JKSessionScreen = () => {
  const logger = console; // Replace with another logging mechanism if needed
  const dispatch = useDispatch();
  const app = useJamKazamApp();
  const { currentUser } = useAuth();

  const {
    guardAgainstInvalidConfiguration,
    guardAgainstActiveProfileMissing,
    guardAgainstSinglePlayerProfile,
    resyncAudio,
  } = useGearUtils();

  const { initialize: initializeMixer, onSessionChange } = useMixerStore();
  const mixerHelper = useMixersContext();
  const { isConnected,
    ConnectionStatus,
    connectionStatus,
    reconnectAttempts,
    lastError,
    jamClient,
    server,
    registerMessageCallback,
    unregisterMessageCallback } = useJamServerContext();

  // Phase 4: Replace CurrentSessionContext with Redux
  const currentSession = useSelector(selectActiveSession);
  const sessionId = useSelector(selectSessionId);
  const inSessionFlag = useSelector(selectInSession);

  // Create ref for line 225, 365 compatibility
  const currentSessionIdRef = useRef(sessionId);
  useEffect(() => {
    currentSessionIdRef.current = sessionId;
  }, [sessionId]);

  const inSession = useCallback(() => inSessionFlag, [inSessionFlag]);

  const { globalObject, metronomeState, updateMetronomeState, closeMetronome, resetMetronome } = useGlobalContext();
  const { getCurrentRecordingState, reset: resetRecordingState, stopRecording } = useRecordingHelpers(jamClient);
  const { SessionPageEnter } = useSessionUtils();

  // Redux recording state
  const currentlyRecording = useSelector(selectIsRecording);
  const [isStoppingRecording, setIsStoppingRecording] = useState(false);

  // Redux media state and actions
  const mediaSummary = useSelector(selectMediaSummary);
  const metronomeTrackMixers = useSelector(selectMetronomeTrackMixers);
  const { openBackingTrack, openMetronome: openMetronomeAction, loadJamTrack, closeMedia } = useMediaActions();

  // Use the session model hook
  const sessionModel = useSessionModel(app, server, null); // sessionScreen is null for now
  const sessionHelper = useSessionHelper();
  const { id: sessionIdFromUrl } = useParams(); // Renamed to avoid conflict with Redux sessionId
  const history = useHistory();

  // Redux session lifecycle state
  const activeSession = useSelector(selectActiveSession);
  const joinStatus = useSelector(selectJoinStatus);
  const hasJoined = useSelector(selectHasJoined);
  const sessionGuardsPassed = useSelector(selectGuardsPassed);
  const userTracks = useSelector(selectUserTracks);
  const mixersReady = useSelector(selectMixersReady);
  const showConnectionAlert = useSelector(selectShowConnectionAlert);
  const reduxSessionId = useSelector(selectSessionId);

  // Non-lifecycle state (keeping as local for now)
  const [requestingSessionRefresh, setRequestingSessionRefresh] = useState(false);
  const [pendingSessionRefresh, setPendingSessionRefresh] = useState(false);

  // Redux modal state
  const showSettingsModal = useSelector(selectModal('settings'));
  const showInviteModal = useSelector(selectModal('invite'));
  const showVolumeModal = useSelector(selectModal('volume'));
  const showRecordingModal = useSelector(selectModal('recording'));
  const showLeaveModal = useSelector(selectModal('leave'));
  const showJamTrackModal = useSelector(selectModal('jamTrack'));
  const showBackingTrackPopup = useSelector(selectModal('backingTrack'));
  const showMediaControlsPopup = useSelector(selectModal('mediaControls'));

  // Non-modal state for settings modal
  const [settingsLoading, setSettingsLoading] = useState(false);

  // Non-modal state for invite modal
  const [friends, setFriends] = useState([]);
  const [sessionInvitees, setSessionInvitees] = useState([]);
  const [inviteLoading, setInviteLoading] = useState(false);

  // Non-modal state for volume modal
  const [volumeLevel, setVolumeLevel] = useState(100);

  // Non-modal state for leave modal
  const [leaveRating, setLeaveRating] = useState(null); // null, 'thumbsUp', 'thumbsDown'
  const [leaveComments, setLeaveComments] = useState('');
  const [leaveLoading, setLeaveLoading] = useState(false);

  //state for video button
  const [videoLoading, setVideoLoading] = useState(false);

  // State for resync button
  const [resyncLoading, setResyncLoading] = useState(false);

  // Redux backing track state (modal visibility and data)
  const backingTrackData = useSelector(selectBackingTrackData);
  const showBackingTrackPlayer = Boolean(backingTrackData);

  // Redux upload state for attachments
  const isUploading = useSelector(selectIsUploading);
  const uploadError = useSelector(selectUploadError);
  const uploadFileName = useSelector(selectUploadFileName);
  const uploadStatus = useSelector(selectUploadStatus);
  const prevUploadStatusRef = useRef(uploadStatus);

  // File input ref for attach button
  const attachFileInputRef = useRef(null);

  // Stable callback for backing track popup close
  const handleBackingTrackClose = useCallback(() => {
    // console.log('JKSessionScreen: Backing Track Popup closing');
    dispatch(closeModal('backingTrack'));
    dispatch(clearBackingTrackData());
  }, [dispatch]);

  // Stable callback for backing track close in main screen
  const handleBackingTrackMainClose = useCallback(async () => {
    // console.log('JKSessionScreen: Backing Track main screen close requested');
    await closeMedia();
    // Also clear the backing track popup data
    dispatch(clearBackingTrackData());
    dispatch(closeModal('backingTrack'));
  }, [closeMedia, dispatch]);

  // Redux JamTrack player state (modal visibility and data)
  const jamTrackData = useSelector(selectJamTrackData);
  const showJamTrackPlayer = Boolean(jamTrackData);

  // Redux jam track state
  const selectedJamTrack = useSelector(selectSelectedJamTrack);
  const jamTrackStems = useSelector(selectJamTrackStems);
  const jamTrackDownloadState = useSelector(state => state.media.downloadState);

  // JamTrack close handler (used by both player and session screen)
  const handleJamTrackClose = useCallback(async () => {
    // console.log('Closing jam track');
    try {
      // Call the close jam track API
      await closeJamTrack({ id: currentSession.id });

      // Clear the selected jam track and stems (session screen)
      dispatch(setSelectedJamTrack(null));
      dispatch(setJamTrackStems([]));

      // Clear the player popup data
      dispatch(clearJamTrackData());

      toast.success('JamTrack closed successfully');
    } catch (error) {
      // console.error('Error closing jam track:', error);
      toast.error('Failed to close JamTrack');
    }
  }, [currentSession, dispatch]);

  // Metronome close handler (used by both popup and session track)
  const handleMetronomeClose = useCallback(async () => {
    // console.log('JKSessionScreen: Closing metronome');
    try {
      // Call jamClient to close metronome
      await jamClient.SessionCloseMetronome();

      // Update local metronome state
      if (closeMetronome) {
        closeMetronome();
      }

      toast.success('Metronome closed successfully');
    } catch (error) {
      // console.error('Error closing metronome:', error);
      toast.error('Failed to close metronome');
    }
  }, [jamClient, closeMetronome]);

  // Stable callback for JamTrack player popup close (WindowPortal X button or ESC)
  const handleJamTrackPlayerClose = useCallback(async () => {
    // console.log('JKSessionScreen: JamTrack Player Popup closing');
    // Call the full close handler to clean up everything
    await handleJamTrackClose();
  }, [handleJamTrackClose]);

  // State for media controls popup (modal visibility now in Redux)
  const [mediaControlsOpened, setMediaControlsOpened] = useState(false);
  const [popupGuard, setPopupGuard] = useState(false); // Hard guard against infinite loops

  // Store references to registered message callbacks for cleanup
  const [registeredCallbacks, setRegisteredCallbacks] = useState([]);
  const registeredCallbacksRef = useRef([]);

  // Function to unregister message callbacks
  // Uses ref for cleanup to avoid stale closure issues when component unmounts
  const unregisterMessageCallbacks = useCallback(() => {
    const callbacks = registeredCallbacksRef.current;
    callbacks.forEach(({ type, callback }) => {
      unregisterMessageCallback(type, callback);
    });
    registeredCallbacksRef.current = [];
    setRegisteredCallbacks([]);
  }, [unregisterMessageCallback]);

  // Fetch session data when URL sessionId changes
  useEffect(() => {
    if (sessionIdFromUrl && sessionIdFromUrl !== sessionId) {
      // console.log('Fetching active session:', sessionIdFromUrl);
      dispatch(fetchActiveSession(sessionIdFromUrl));
    }
  }, [sessionIdFromUrl, sessionId, dispatch]);

  // Setup global alert callback handler for backend mixer changes
  useEffect(() => {
    if (!window.JK) window.JK = {};

    // Alert callback handler - routes backend alerts to appropriate handlers
    const alertCallbackHandler = (type, text) => {
      // console.log('[AlertCallback] type:', type, 'text:', text);
      // BACKEND_MIXER_CHANGE = 2
      if (type === 2) {
        // console.log('[AlertCallback] BACKEND_MIXER_CHANGE:', text);
        if (sessionModel && sessionModel.onBackendMixerChanged) {
          sessionModel.onBackendMixerChanged(type, text);
        }
      }
      // Alert code 40: Audio profile issues (common with built-in audio)
      if (type === 40) {
        // console.warn('[AlertCallback] Audio profile alert (40):', text);
      }
      // Handle other alert types as needed
    };

    // Register under both names (native client may use either)
    window.JK.HandleAlertCallback = alertCallbackHandler;
    window.JK.AlertCallback = alertCallbackHandler;

    return () => {
      if (window.JK) {
        delete window.JK.HandleAlertCallback;
      }
    };
  }, [sessionModel]);

  useEffect(() => {
    if (!isConnected || !jamClient) return;
    // console.debug("-DEBUG- JKSessionScreen: isConnected changed to true");

    guardOnJoinSession();
  }, [isConnected, jamClient]); // Added jamClient to dependencies for stability

  const guardOnJoinSession = async () => {
    // console.log("-DEBUG- JKSessionScreen: guardOnJoinSession")
    try {
      const musicSessionResp = await getSessionHistory(sessionIdFromUrl);
      const musicSession = await musicSessionResp.json();
      // logger.log("-DEBUG- JKSessionScreen: fetched session history: ", musicSession);
      dispatch(setSessionId(musicSession.id)); // Phase 4: dispatch to Redux

      const musicianAccessOnJoin = musicSession.musician_access;
      const shouldVerifyNetwork = musicSession.musician_access;
      const clientRole = await jamClient.getClientParentChildRole();

      // logger.log("-DEBUG- JKSessionScreen: musicianAccessOnJoin when joining session: " + musicianAccessOnJoin);
      // logger.log("-DEBUG- JKSessionScreen: clientRole when joining session: " + clientRole);
      // logger.log("-DEBUG- JKSessionScreen: currentSessionId when joining session: " + currentSessionIdRef.current);

      if (clientRole === CLIENT_ROLE.CHILD) {
        // logger.debug("-DEBUG- JKSessionScreen: client is configured to act as child. skipping all checks. assuming 0 tracks");
        dispatch(setUserTracks([]));

        //skipping all checks. assuming 0 tracks
        await joinSession();
        return;
      }

      try {
        await guardAgainstInvalidConfiguration(app, shouldVerifyNetwork);
        const result = await SessionPageEnter();
        // logger.log("-DEBUG- JKSessionScreen: SessionPageEnter result: ", result);

        try {
          await guardAgainstActiveProfileMissing(app, result);
          // logger.log("-DEBUG- JKSessionScreen: user has an active profile");
          try {
            const tracks = await sessionModel.waitForSessionPageEnterDone();
            dispatch(setUserTracks(tracks));
            // logger.log("-DEBUG- JKSessionScreen: userTracks: ", tracks);
            try {
              await ensureAppropriateProfile(musicianAccessOnJoin)
              // logger.log("-DEBUG- JKSessionScreen: user has passed all session guards")

              dispatch(setGuardsPassed(true))

            } catch (error) {
              // logger.error("-DEBUG- JKSessionScreen: User profile is not appropriate for session:", error);
              // If error doesn't control navigation, show alert (legacy behavior shows dialog)
              if (!error?.controlled_location) {
                // logger.debug("-DEBUG- JKSessionScreen: Profile guard failed, showing alert");
                // TODO: Replace with proper JKSessionProfileDialog component
                alert(
                  'Your current audio profile is not suitable for multi-user sessions.\n\n' +
                  'Please configure a proper audio interface in your audio settings, ' +
                  'or create a private session for solo practice.'
                );
                await sessionModel.handleLeaveSession();
                history.push('/'); // Redirect to dashboard
                return; // Don't continue with session join
              }
              // If controlled_location is true, the error handler already redirected
            }
          } catch (error) {
            // logger.error("-DEBUG- JKSessionScreen: Error: waiting for session page enter to complete:", error);
            if (error === "timeout") {
              //TODO: show some error
            } else if (error === 'session_over') {
              // do nothing; session ended before we got the user track  info. just bail
              // logger.debug("-DEBUG- JKSessionScreen: Error:: session is over; bailing");
            } else {
              //TODO: show some error
            }

            await sessionModel.handleLeaveSession(); //TODO: handle redirection
          }
        } catch (error) {
          // Active profile is missing, redirect to home or if the error has a location, redirect there
          // logger.error("-DEBUG- JKSessionScreen: Error: Active profile is missing or invalid:", error);
        }
      } catch (error) {
        // Invalid configuration, redirect to home
        await sessionModel.handleLeaveSession(); //TODO: handle redirection
        // logger.error("-DEBUG- JKSessionScreen: Error: Invalid configuration:", error);
      }

    } catch (error) {
      // logger.error("-DEBUG- JKSessionScreen: Error: Error fetching session history:", error);
      //TODO: Show some error
    }
  };

  useEffect(() => {
    if (!sessionGuardsPassed || userTracks.length === 0 || hasJoined) { return }
    joinSession();
  }, [sessionGuardsPassed, userTracks, hasJoined])

  // Track sync: Sync tracks to server when session joined (3-call pattern matching legacy)
  // IMPORTANT: Wait for mixers to be ready before syncing to avoid race condition with mixer initialization
  useEffect(() => {
    if (!hasJoined || !sessionId || !server?.clientId || !mixersReady) {
      return;
    }

    // console.log('[Track Sync] Mixers ready, scheduling track sync calls');

    // First sync: Initial setup (~1s after join)
    const timer1 = setTimeout(() => {
      dispatch(syncTracksToServer(sessionId, server.clientId));
    }, 1000);

    // Second sync: Refinement (~1.4s after join)
    const timer2 = setTimeout(() => {
      dispatch(syncTracksToServer(sessionId, server.clientId));
    }, 1400);

    // Third sync: Final config (~6s after join)
    const timer3 = setTimeout(() => {
      dispatch(syncTracksToServer(sessionId, server.clientId));
    }, 6000);

    // Cleanup timers on unmount or if hasJoined/sessionId changes
    return () => {
      clearTimeout(timer1);
      clearTimeout(timer2);
      clearTimeout(timer3);
    };
    // Note: server intentionally NOT in deps to avoid re-running on server reference changes
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [hasJoined, sessionId, mixersReady, dispatch])

  // Fetch chat history when session joins to populate unread badge
  // This ensures unread count persists across page reloads
  useEffect(() => {
    if (!hasJoined || !sessionId) {
      return;
    }

    dispatch(fetchChatHistory({
      channel: sessionId,
      sessionId: sessionId
    }));
  }, [hasJoined, sessionId, dispatch]);

  const joinSession = async () => {

    await jamClient.SetVURefreshRate(150);
    await jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
    await jamClient.SessionSetAlertCallback("JK.HandleAlertCallback");
    await jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");

    await jamClient.SessionSetConnectionStatusRefreshRate(1000);

    let clientRole = await jamClient.getClientParentChildRole();
    const parentClientId = await jamClient.getParentClientId();
    // console.debug('role when joining session: ' + clientRole + ', parentClientId: ' + parentClientId);



    if (clientRole === 0) {
      clientRole = 'child';
    } else if (clientRole === 1) {
      clientRole = 'parent';
    }

    if ((clientRole === '') || !clientRole) {
      clientRole = null;
    }

    // subscribe to events from the recording model
    //this.recordingRegistration(); //TODO: implement recording registration

    // tell the server we want to join



    //const clientId = await jamClient.clientID();
    const clientId = server.clientId;
    // console.log("joining session " + sessionId + " as client " + JSON.stringify(clientId) + " with role " + clientRole + " and parent client " + parentClientId);

    const latency = await jamClient.FTUEGetExpectedLatency().latency

    // console.log("joinSession parameters: ", {
    //   client_id: clientId,
    //   ip_address: server.publicIP,
    //   as_musician: true,
    //   tracks: userTracks,
    //   session_id: sessionId,
    //   client_role: clientRole,
    //   parent_client_id: parentClientId,
    //   audio_latency: latency,
    // });

    joinSessionRest({
      client_id: clientId,
      ip_address: server.publicIP,
      as_musician: true,
      tracks: userTracks,
      session_id: sessionId,
      client_role: clientRole,
      parent_client_id: parentClientId,
      audio_latency: latency,
    }).then(async (response) => {
      // console.debug("joinSessionRest response received", response.errors);
      if (response.errors) {
        throw new Error("Unable to join session: " + JSON.stringify(response.errors));
      } else {

        const data = await response.json();
        // console.debug("join session response xxx: ", data);

        // Update Redux state - user has successfully joined
        dispatch(joinActiveSession.fulfilled(data, '', { sessionId, options: {} }));

        if (!inSession()) {
          // the user has left the session before they got joined.  We need to issue a leave again to the server to make sure they are out
          // logger.debug("user left before fully joined to session.  telling server again that they have left");
          sessionModel.leaveSessionRest();
        }

        sessionModel.updateSessionInfo(data, true);
        dispatch(updateSessionData(data)); // Phase 4: dispatch to Redux

        //TODO: revist this logic later
        // on temporary disconnect scenarios, a user may already be in a session when they enter this path
        // so we avoid double counting
        // if (!this.alreadyInSession()) {
        //   if (this.participants().length === 1) {
        //     context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.create);
        //   } else {
        //     context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
        //   }
        // }

        // this.recordingModel.reset(this.currentSessionId); //TODO: implement recording model

        const joinSessionMsg = {
          sessionID: currentSession.id,
          music_session_id_int: response.music_session_id_int
        };

        await jamClient.JoinSession(joinSessionMsg);

        //@refreshCurrentSession(true);

        // Chat message handler - receives messages from WebSocket and dispatches to Redux
        // Callback signature: (fullMessage, payload) where payload contains the chat_message data
        const handleChatMessage = (fullMessage, payload) => {
          // Transform Protocol Buffer format to Redux format
          // payload contains: {sender_name, sender_id, msg, msg_id, created_at, channel, ...}
          const message = {
            id: payload.msg_id,
            senderId: payload.sender_id,
            senderName: payload.sender_name,
            message: payload.msg,
            createdAt: payload.created_at,
            channel: payload.channel || 'session',
            sessionId: currentSession.id,
            // Attachment fields (null/undefined if not an attachment)
            purpose: payload.purpose,              // 'Notation File', 'Audio File', or undefined
            attachmentId: payload.attachment_id,   // MusicNotation UUID
            attachmentType: payload.attachment_type, // 'notation' or 'audio'
            attachmentName: payload.attachment_name, // filename
            attachmentSize: payload.attachment_size  // file size in bytes (may be null)
          };
          dispatch(addMessageFromWebSocket(message));
        };

        // Register message callbacks and store references for cleanup
        const callbacksToRegister = [
          { type: MessageType.SESSION_JOIN, callback: trackChanges },
          { type: MessageType.SESSION_DEPART, callback: trackChanges },
          { type: MessageType.TRACKS_CHANGED, callback: trackChanges },
          { type: MessageType.HEARTBEAT_ACK, callback: trackChanges },
          { type: MessageType.CHAT_MESSAGE, callback: handleChatMessage }
        ];

        callbacksToRegister.forEach(({ type, callback }) => {
          registerMessageCallback(type, callback);
        });

        // Store registered callbacks for cleanup (both state and ref for reliable cleanup)
        setRegisteredCallbacks(callbacksToRegister);
        registeredCallbacksRef.current = callbacksToRegister;

        //TODO: revist the logic in following commented section
        //if (document) { $(document).trigger(EVENTS.SESSION_STARTED, { session: { id: this.currentSessionId, lesson_session: response.lesson_session } }); }

        // this.handleAutoOpenJamTrack();

        // this.watchBackendStats();

        // ConfigureTracksActions.reset(true);
        // this.delayEnableVst();
        // logger.debug("completed session join")

      }

    }).catch((xhr) => {
      // console.error("joinSessionRest error: ", xhr);
      let leaveBehavior;
      sessionModel.updateCurrentSession(null);

      if (xhr.status === 404) {
        // we tried to join the session, but it is already gone.  kick user back to join session screen

      } else if (xhr.status === 422) {
        //console.error("unable to join session - 422 error");
        // const response = JSON.parse(xhr.responseText);
        // if (response["errors"] && response["errors"]["tracks"] && (response["errors"]["tracks"][0] === "Please select at least one track")) {
        //   // You will need to reconfigure your audio device. show an alert
        // } else if (response["errors"] && response["errors"]["music_session"] && (response["errors"]["music_session"][0] == ["is currently recording"])) {
        //   //The session is currently recording. You can not join a session that is recording. show an alert
        // } else if (response["errors"] && response["errors"]["remaining_session_play_time"]) {
        //   //user has no available playtime. upgrade 
        // } else if (response["errors"] && response["errors"]["remaining_month_play_time"]) {
        //   //user has no available playtime. upgrade
        // } else {
        //   // unknown 422 error. alert unable to join sessio
        // }
      } else {
        // unable to join session
      }
    })

  }

  useEffect(() => {
    if (!isConnected || !hasJoined) return;
    onSessionChange(sessionHelper);
  }, [isConnected, hasJoined, sessionHelper.id()]);

  const ensureAppropriateProfile = async (musicianAccess) => {
    return new Promise(async function (resolve, reject) {
      if (musicianAccess) {
        try {
          await guardAgainstSinglePlayerProfile(app);
          resolve();
        } catch (error) {
          reject(error)
        }
      } else {
        resolve();
      }
    })
  };

  // Use sessionModel functions
  const trackChanges = sessionModel.trackChanges;
  const refreshCurrentSession = sessionModel.refreshCurrentSession;
  const updateSession = sessionModel.updateSession;

  const musicianAccess = useMemo(() => {
    if (!currentSession) return null;
    return sessionModel.getMusicianAccess();
  }, [currentSession]);

  // Memoize chat object to prevent unnecessary re-renders
  const chat = useMemo(() => {
    if (!mixerHelper.chatMixer) return null;
    return {
      mixers: mixerHelper.chatMixer,
      mode: mixerHelper.mixMode,
      photoUrl: mixerHelper.myTracks.length > 0 ? mixerHelper.myTracks[0].photoUrl : '',
      name: 'Chat'
    };
  }, [mixerHelper.chatMixer, mixerHelper.mixMode, mixerHelper.myTracks]);

  // useEffect(() => {
  //   if (!isConnected) return;
  //   // validate session by fetching the session from the server
  //   fetchSession();
  // }, [isConnected]);

  // const fetchSession = async () => {
  //  const session = await getSession(sessionId);
  //   if (session) {
  //     setSessionState(prevState => ({
  //       ...prevState,
  //       ...session
  //     }));
  //   } else {
  //     logger.error("Invalid session ID or unable to fetch session");
  //     //TODO: Handle invalid session (e.g., redirect or show error)
  //   }
  // };

  // Monitor connection status changes
  useEffect(() => {
    if (connectionStatus === ConnectionStatus.DISCONNECTED ||
      connectionStatus === ConnectionStatus.ERROR) {
      dispatch(setConnectionStatus('disconnected'));
    } else if (connectionStatus === ConnectionStatus.CONNECTED) {
      dispatch(setConnectionStatus('connected'));
    } else if (connectionStatus === ConnectionStatus.RECONNECTING) {
      dispatch(setConnectionStatus('reconnecting'));
    }
  }, [connectionStatus, dispatch]);

  // Handlers for recording and playback
  // const handleStartRecording = () => {
  //   jamClient.StartRecording({ recordingId: `rec_${Date.now()}` });
  // };

  // const handleStopRecording = () => {
  //   jamClient.StopRecording({});
  // };

  // const handlePlayPause = () => {
  //   if (isPlaying) {
  //     jamClient.SessionPausePlay();
  //   } else {
  //     jamClient.SessionStartPlay();
  //   }
  // };

  // const handleStopPlayback = () => {
  //   jamClient.SessionStopPlay();
  // };

  // // Callback handlers (these would be implemented to handle WebSocket responses)
  // const HandleSessionCallback = (data) => {
  //   logger.log('Session callback:', data);
  //   // Handle session events
  // };

  // const HandleRecordingStarted = (data) => {
  //   logger.log('Recording started:', data);
  //   // Update recording state
  // };

  // const HandleRecordingStopped = (data) => {
  //   logger.log('Recording stopped:', data);
  //   // Update recording state
  // };

  // const HandleVolumeChangeCallback = (mixerId, isLeft, value, isMuted) => {
  //   logger.log('Volume changed:', { mixerId, isLeft, value, isMuted });
  //   // Update mixer state
  // };

  // const HandleBridgeCallback = (vuData) => {
  //   logger.log('Bridge callback:', vuData);
  //   // Handle VU meter updates
  // };



  useEffect(() => {
    fetchFriends();
  }, []);

  const fetchFriends = () => {
    if (currentUser) {
      getFriends(currentUser.id)
        .then(resp => {
          if (resp.ok) {
            return resp.json();
          }
        })
        .then(data => {
          setFriends(data);
        });
    }
  };

  const handleRecordingSubmit = async (settings) => {
    settings.volume = getCurrentRecordingState().inputVolumeLevel;
    try {
      localStorage.setItem("recordSettings", JSON.stringify(settings));
    } catch (e) {
      logger.info("error while saving recordSettings to localStorage");
      logger.log(e.stack);
    }

    const params = {
      recordingType: settings.recordingType,
      name: settings.recordingName,
      audioFormat: settings.audioFormat,
      audioStoreType: settings.audioStoreType,
      includeChat: settings.includeChat,
      volume: settings.volume,
    };

    if (params.recordingType === RECORD_TYPE_BOTH) {
      params['videoFormat'] = settings.videoFormat;
      params['audioDelay'] = settings.audioDelay;

      const obsAvailable = await jamClient.IsOBSAvailable();

      if (!obsAvailable) {
        toast.warning("OBS Studio is not available. Please ensure OBS Studio is installed and running to record video.");
        return;
      }

      if (!globalObject.JK.videoIsOngoing) {
        toast.warning("To make a video recording in JamKazam you must have an ongoing video. You can start a video by clicking the Video button on session tool bar.");
        return;
      }
    }
    //this.startStopRecording(params);
    //TODO: handle startStopRecording
    doStartRecording(params);

  }

  function groupTracksToClient(recording) {
    // group N tracks to the same client Id
    let groupedTracks = {};
    let recordingTracks = recording["recorded_tracks"];
    for (let i = 0; i < recordingTracks.length; i++) {
      let clientId = recordingTracks[i].client_id;

      let tracksForClient = groupedTracks[clientId];
      if (!tracksForClient) {
        tracksForClient = [];
        groupedTracks[clientId] = tracksForClient;
      }
      tracksForClient.push(recordingTracks[i]);
    }
    return dkeys(groupedTracks);
  }

  const doStartRecording = (params) => {
    startRecording({ music_session_id: currentSession.id, recordVideo: params.recordVideo }).then(async (recording) => {
      const currentRecordingId = recording.id;
      // console.debug("Recording started with ID: ", currentRecordingId);
      const groupedTracks = groupTracksToClient(recording);
      try {
        await jamClient.StartMediaRecording(currentRecordingId, groupedTracks, params);
      } catch (error) {
        // console.error("Error starting media recording:", error);
      }
    }).catch((error) => {
      // console.error("Error starting recording:", error);
    });
  }

  const handleLeaveSession = () => {
    // Just show the modal - no leave operations yet
    dispatch(openModal('leave'));
  };

  const handleLeaveSubmit = async (feedbackData) => {
    try {
      setLeaveLoading(true);

      // Unregister message callbacks before leaving
      unregisterMessageCallbacks();

      // Close metronome if open before leaving
      if (metronomeState.isOpen) {
        // console.log('Closing metronome before leaving session');
        closeMetronome();
      }

      // Submit feedback to backend first
      const clientId = server.clientId;
      const backendDetails = jamClient.getAllClientsStateMap ? jamClient.getAllClientsStateMap() : {};

      await submitSessionFeedback(clientId, {
        rating: feedbackData.rating,
        comment: feedbackData.comments,
        backend_details: backendDetails
      });

      // Then perform leave operations
      await sessionModel.handleLeaveSession();

      // Clear Redux chat state to prevent stale data
      dispatch(clearAllMessages());

      // Clear Redux session state
      dispatch(clearSession());

      dispatch(closeModal('leave'));
      toast.success('Thank you for your feedback!');

      // Navigate to sessions page using React Router
      history.push('/sessions');
    } catch (error) {
      // console.error('Error submitting feedback or leaving session:', error);
      toast.error('Failed to submit feedback or leave session');
    } finally {
      setLeaveLoading(false);
    }
  };

  // Cleanup metronome state and message callbacks when component unmounts
  useEffect(() => {
    return () => {
      // Unregister message callbacks
      unregisterMessageCallbacks();

      // Reset metronome state when component unmounts (session ends)
      if (metronomeState.isOpen) {
        // console.log('Resetting metronome state on session cleanup');
        resetMetronome();
      }

      // Clear Redux chat state to prevent stale data
      dispatch(clearAllMessages());

      // Clear Redux session state on unmount
      dispatch(clearSession());
    };
  }, [metronomeState.isOpen, resetMetronome, dispatch]);

  // Check if user can use video (subscription/permission check)
  const canVideo = () => {
    // This would need to be implemented based on user subscription logic
    // console.debug("JKSessionScreen: Checking video permission for user", currentSession);
    return currentSession?.can_use_video || false;
  };

  // Open external link in new window/tab
  const openExternalLink = (url) => {
    window.open(url, '_blank', 'noopener,noreferrer');
  };

  // Handle video button click - opens new video conferencing server
  const handleVideoClick = async () => {
    if (!canVideo()) {
      // Show upgrade modal/banner
      showVideoUpgradePrompt();
      return;
    }

    try {
      setVideoLoading(true);

      // Get video conferencing room URL from server
      const response = await getVideoConferencingRoomUrl(currentSession.id);
      const videoUrl = `${response.url}&audiooff=true`;

      // Open video URL in new browser window/tab
      // console.debug("JKSessionScreen: Opening video conferencing URL", videoUrl);
      openExternalLink(videoUrl);

    } catch (error) {
      // console.error('Failed to get video room URL:', error);
      // Handle error - could show error message to user
      toast.error('Failed to start video session');
    } finally {
      // Keep loading state for 10 seconds to prevent multiple clicks
      setTimeout(() => setVideoLoading(false), 10000);
    }
  };

  // Show upgrade prompt for users without video permissions
  const showVideoUpgradePrompt = () => {
    // Implementation for showing upgrade modal/banner
    // This could use a modal context or toast notification
    // console.log('Show video upgrade prompt');
    toast.warning(<VideoUpgradeContent />, { autoClose: false });
  };

  const VideoUpgradeContent = () => (
    <div>
      <h5>Upgrade Required</h5>
      <p>The video feature requires a premium subscription. Please upgrade your plan to access video conferencing.</p>
      <Button color="primary" onClick={() => {
        // Redirect to upgrade page
        history.push('/upgrade');
      }}>
        Upgrade Now
      </Button>
    </div>
  );

  const handleBroadcast = async (e) => {
    e.preventDefault();
    try {
      await jamClient.LaunchBroadcastSettings();
    } catch (e) {
      // console.error(e);
    }
  };

  // Handle Record button click - opens modal to start recording, or stops recording if already recording
  const handleRecordButtonClick = async () => {
    if (currentlyRecording) {
      // Stop recording directly without opening modal
      if (isStoppingRecording) return; // Prevent double-clicks
      setIsStoppingRecording(true);
      try {
        await stopRecording();
        toast.info('Recording stopped.');
      } catch (error) {
        console.error('Failed to stop recording:', error);
        toast.error('Failed to stop recording.');
      } finally {
        setIsStoppingRecording(false);
      }
    } else {
      // Open modal to start recording
      dispatch(openModal('recording'));
    }
  };

  // Handle Resync button click - performs audio resync via native client
  const handleResync = useCallback(async (e) => {
    e.preventDefault();
    if (resyncLoading) return;

    setResyncLoading(true);
    try {
      await resyncAudio();
      // Silent success (matches legacy behavior)
    } catch (error) {
      // console.error('Audio resync failed:', error);
      if (error.message === 'timeout') {
        toast.error('Audio resync timed out. Please try again.');
      } else {
        toast.error('Audio resync failed: ' + (error.message || 'Unknown error'));
      }
    } finally {
      setResyncLoading(false);
    }
  }, [resyncAudio, resyncLoading]);

  // Attach button handlers
  const handleAttachClick = useCallback(() => {
    if (attachFileInputRef.current) {
      attachFileInputRef.current.click();
    }
  }, []);

  const handleFileSelect = useCallback((e) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // Reset input for re-selection of same file
    e.target.value = '';

    // Validate file before upload
    const validation = validateFile(file);

    if (!validation.valid) {
      toast.error(validation.error);
      return;
    }

    // Show warnings if any (e.g., backend may not fully support this type)
    if (validation.warnings) {
      validation.warnings.forEach(warning => {
        // console.warn('Attachment warning:', warning);
      });
    }

    // Open chat window if not already open
    dispatch(openModal('chat'));

    // Dispatch upload with user info for optimistic update
    // (sender is excluded from WebSocket broadcast, so we add message locally)
    dispatch(uploadAttachment({
      file,
      sessionId,
      clientId: server?.clientId,
      userId: currentUser?.id,
      userName: currentUser?.name
    }));
  }, [dispatch, sessionId, server?.clientId, currentUser?.id, currentUser?.name]);

  // Show error toast when upload fails
  useEffect(() => {
    if (uploadError) {
      toast.error(uploadError);
      dispatch(clearUploadError());
    }
  }, [uploadError, dispatch]);

  // Show success toast when upload completes
  useEffect(() => {
    // Show success toast when upload transitions from 'uploading' to 'idle'
    if (prevUploadStatusRef.current === 'uploading' && uploadStatus === 'idle') {
      toast.success('File uploaded successfully', { autoClose: 3000 });
    }
    prevUploadStatusRef.current = uploadStatus;
  }, [uploadStatus]);

  const handleBackingTrackSelected = async (result) => {
    // console.log('JKSessionScreen: handleBackingTrackSelected called with:', result);
    // console.log('JKSessionScreen: Current state - showBackingTrackPopup:', showBackingTrackPopup, 'popupGuard:', popupGuard);

    // Don't proceed if no file was selected (user cancelled the dialog)
    if (!result || !result.file) {
      console.log('JKSessionScreen: No file selected, ignoring');
      return;
    }

    try {
      // Use the openBackingTrack action from useMediaActions (already destructured at line 153)
      // This handles: jamClient call, Redux state update, and server sync
      await openBackingTrack(result.file);

      // Set up data for the popup (don't store jamClient in Redux - it's not serializable)
      // console.log('JKSessionScreen: Setting backing track data...');
      dispatch(setBackingTrackData({
        backingTrack: result.file,
        session: currentSession,
        currentUser: currentUser
      }));

      // Show the popup
      // console.log('JKSessionScreen: Setting showBackingTrackPopup to true...');
      dispatch(openModal('backingTrack'));
      // console.log('JKSessionScreen: handleBackingTrackSelected completed successfully');
      //TODO:  In the legacy client, the popup window was opened as a native window through the client. decide whether we need to replicate that behavior here or do it through the browser only
    } catch (error) {
      // console.error('JKSessionScreen: Error opening backing track:', error);
      toast.error('Failed to open backing track');
    }
  };

  const handleJamTrackSelect = async (jamTrack) => {
    // console.log('Jam track selected:', jamTrack);
    try {
      // Fetch jam track details with stems
      const response = await getJamTrack({ id: jamTrack.id });
      const jamTrackWithStems = await response.json();

      // console.log('Jam track data:', jamTrackWithStems);

      // Check sync state BEFORE dispatching UI state
      // This transitions downloadState from 'idle' to 'synchronized' (if already synced)
      // or keeps it 'idle' (if download needed)
      const syncResult = await dispatch(checkJamTrackSync({
        jamTrack: jamTrackWithStems,
        jamClient
      })).unwrap();

      // Set the selected jam track and stems (for display on session screen)
      dispatch(setSelectedJamTrack(jamTrackWithStems));
      dispatch(setJamTrackStems(jamTrackWithStems.tracks || []));

      // Open the JamTrack player popup (with full data needed for player)
      dispatch(setJamTrackData({
        jamTrack: jamTrackWithStems,
        session: currentSession,
        currentUser: currentUser
      }));

      // Handle sync result
      if (syncResult.isSynchronized) {
        // Already synchronized - show success
        toast.success(`Loaded JamTrack: ${jamTrackWithStems.name}`);
      } else {
        // Not synchronized - start download flow
        // This will transition state through checking → packaging → downloading → keying → synchronized
        toast.info(`Loading JamTrack: ${jamTrackWithStems.name}...`);
        loadJamTrack(jamTrackWithStems);
      }
    } catch (error) {
      // console.error('Error loading jam track:', error);
      toast.error('Failed to load JamTrack');
    }
  };

  const handleMetronomeSelected = async () => {
    // console.log('Opening metronome');
    try {
      // Check if currently recording - can't open metronome while recording
      if (currentlyRecording) {
        toast.warning("You can't open a metronome while recording.");
        return;
      }

      // Check for unstable NTP clocks (like legacy implementation)
      const unstableClocks = await checkUnstableClocks();
      if (currentSession.participants && currentSession.participants.length > 1 && unstableClocks.length > 0) {
        const names = unstableClocks.join(", ");
        toast.warning(`Couldn't open metronome due to unstable clocks: ${names}`);
        return;
      }

      // Track analytics (like legacy SessionStore)
      if (window.stats && window.stats.write) {
        const data = {
          value: 1,
          session_size: currentSession.participants?.length || 1,
          user_id: currentUser?.id,
          user_name: currentUser?.name
        };
        window.stats.write('web.metronome.open', data);
      }

      // Stop any current playback first (like legacy MixerStore)
      await jamClient.SessionStopPlay();

      // Default metronome settings for the controls
      const bpm = 120;
      const sound = 2; // Beep (numeric value)
      const soundName = "Beep"; // String name for native client
      const meter = 1;
      const cricket = false;

      // console.log(`Opening metronome controls with default settings: bpm=${bpm}, sound=${soundName}, meter=${meter}`);

      // Inform server about metronome opening (like legacy SessionStore)
      await openMetronome({ id: currentSession.id });

      // Initialize metronome track (creates mixers) then stop playback
      // This allows the track to appear in the UI while audio remains stopped
      // Use SessionStopPlay instead of SessionCloseMetronome to preserve metronome state
      await jamClient.SessionOpenMetronome(bpm, soundName, meter, 0);
      await jamClient.SessionStopPlay();

      // Update local metronome state to show popup immediately
      // Metronome track will be visible but audio is stopped (user must click Play)
      if (updateMetronomeState) {
        updateMetronomeState({
          isOpen: true,
          bpm: bpm,
          sound: sound,
          meter: meter,
          cricket: cricket
        });
      }

      toast.success('Metronome opened successfully');
    } catch (error) {
      // console.error('Error opening metronome:', error);
      toast.error('Failed to open metronome');
    }
  };

  const checkUnstableClocks = async () => {
    try {
      const unstable = [];

      // Check current user's NTP stability
      const myState = await jamClient.getMyNetworkState();
      if (!myState.ntp_stable) {
        unstable.push('this computer');
      }

      // Check other participants' NTP stability
      if (currentSession.participants) {
        for (const participant of currentSession.participants) {
          if (participant.client_id !== server.clientId) {
            try {
              const peerState = await jamClient.getPeerState(participant.client_id);
              if (!peerState.ntp_stable) {
                unstable.push(participant.user.first_name + ' ' + participant.user.last_name);
              }
            } catch (error) {
              // Ignore errors for individual peer checks
            }
          }
        }
      }

      return unstable;
    } catch (error) {
      // console.error('Error checking NTP stability:', error);
      return [];
    }
  };

  const handleAudioConfigClick = () => {
    //dispatch(openModal('audioConfig'));
    // console.log('Audio configuration options coming soon');
    alert('Audio configuration modal is not implemented yet');

  }

  return (
    <Card>
      {!isConnected && <div className='d-flex align-items-center'>Connecting to backend...</div>}
      <FalconCardHeader title="Session" titleClass="font-weight-bold">
        <Button color="primary" size="md" onClick={handleLeaveSession}>Leave Session</Button>
      </FalconCardHeader>

      <CardHeader className="bg-light border-bottom border-top py-2 border-3">
        <div className="d-flex flex-nowrap overflow-auto" style={{ gap: '0.5rem', zIndex: 1100 }}>
          <Button className='btn-custom-outline' outline size="md" onClick={() => dispatch(openModal('settings'))}>
            <img src={gearIcon} alt="Settings" style={{ width: '20px', height: '20px', marginRight: '0.3rem' }} />
            Settings</Button>
          <Button className='btn-custom-outline' outline size="md" onClick={() => dispatch(openModal('invite'))}>
            <img src={inviteIcon} alt="Invite" style={{ width: '19px', height: '19px', marginRight: '0.3rem' }} />
            Invite</Button>
          <Button className='btn-custom-outline' outline size="md" onClick={() => dispatch(openModal('volume'))}>
            <img src={volumeIcon} alt="Volume" style={{ width: '20px', height: '20px', marginRight: '0.3rem' }} />
            Volume</Button>
          <Button className='btn-custom-outline' outline size="md" onClick={handleVideoClick} disabled={videoLoading}>
            <img src={videoIcon} alt="Video" style={{ width: '20px', height: '20px', marginRight: '0.3rem' }} />
            {videoLoading && (<Spinner size="sm" />)}
            &nbsp;Video
          </Button>
          <Button
            className={currentlyRecording ? 'btn-recording-active' : 'btn-custom-outline'}
            color={currentlyRecording ? 'danger' : undefined}
            outline={!currentlyRecording}
            size="md"
            onClick={handleRecordButtonClick}
            disabled={isStoppingRecording}
          >
            <img src={recordIcon} alt="Record" style={{ width: '20px', height: '20px', marginRight: '0.3rem' }} />
            {isStoppingRecording ? 'Stopping...' : (currentlyRecording ? 'Recording' : 'Record')}
          </Button>
          <Button className='btn-custom-outline' outline size="md" onClick={handleBroadcast}>
            <img src={broadcastIcon} alt="Broadcast" style={{ width: '21px', height: '21px', marginRight: '0.3rem' }} />
            Broadcast</Button>
          <JKSessionOpenMenu
            onBackingTrackSelected={handleBackingTrackSelected}
            onJamTrackSelected={() => dispatch(openModal('jamTrack'))}
            onMetronomeSelected={handleMetronomeSelected}
            isBackingTrackOpen={showBackingTrackPlayer}
            isJamTrackOpen={showJamTrackPlayer}
            isMetronomeOpen={metronomeState.isOpen}
          />
          <JKSessionChatButton sessionId={sessionId} />
          <input
            type="file"
            ref={attachFileInputRef}
            style={{ display: 'none' }}
            accept=".pdf,.xml,.mxl,.txt,.png,.jpg,.jpeg,.gif,.mp3,.wav"
            onChange={handleFileSelect}
          />
          <Button
            className='btn-custom-outline'
            outline
            size="md"
            onClick={handleAttachClick}
            disabled={isUploading}
          >
            <img src={attachIcon} alt="Attach" style={{ width: '20px', height: '20px', marginRight: '0.3rem' }} />
            {isUploading ? 'Uploading...' : 'Attach'}
          </Button>
          <Button className='btn-custom-outline' outline size="md" onClick={handleResync} disabled={resyncLoading}>
            <img src={resyncIcon} alt="Resync" style={{ width: '20px', height: '20px', marginRight: '0.3rem' }} />
            {resyncLoading ? <><Spinner size="sm" /> Resyncing...</> : 'Resync'}
          </Button>
        </div>
      </CardHeader>

      <CardBody className="pl-4" style={{ backgroundColor: '#eceff9', overflowX: 'auto', width: '100%' }}>
        <div className='d-flex' style={{ gap: '1rem' }}>
          <div className='audioInputs'>
            <h5>Audio Inputs

              {/* <FontAwesomeIcon icon="question-circle" id="audioInputsTooltip" className="ml-2" style={{ cursor: 'pointer' }} /> */}
              <img src={helpIcon} alt="Help" id="audioInputsTooltip" className="ml-2" style={{ cursor: 'pointer', width: '18px', height: '18px' }} />
              <img src={gearIcon} alt="Settings" id="audioInputsSettings" className="ml-2" style={{ cursor: 'pointer', width: '18px', height: '18px' }} onClick={handleAudioConfigClick} />
            </h5>
            <div style={{ borderRight: '1px #ddd solid', paddingRight: '1rem' }}>
              <JKSessionAudioInputs
                myTracks={mixerHelper.myTracks}
                chat={chat}
                mixerHelper={mixerHelper}
                isRemote={false}
                mixType="master"
              />
            </div>
          </div>

          <div className='sessionMix'>
            <h5>Session Mix
              <a id="sessionMixTooltip" style={{ height: 18, width: 18}}>
                <img src={helpIcon} alt="Help" className="ml-2" style={{ cursor: 'pointer', width: '18px', height: '18px' }} />
              </a>
            </h5>
            <div className='d-flex' style={{ gap: '1rem' }}>
              <JKSessionAudioInputs
                myTracks={mixerHelper.myTracks}
                chat={chat}
                mixerHelper={mixerHelper}
                isRemote={true}
                mixType="personal"
              />
              <JKSessionRemoteTracks
                mixerHelper={mixerHelper}
                sessionModel={sessionModel}
              />
            </div>
          </div>

          {/* JamTrack Section - Show stems only when synchronized with backend */}
          {selectedJamTrack && jamTrackStems.length > 0 &&
            (jamTrackDownloadState.state === 'synchronized') && (
              <>
                <div style={{ borderLeft: '1px solid #ddd', paddingLeft: '1rem' }}></div>
                <div className='jamTrack'>
                  <h5>
                    JamTrack: {selectedJamTrack.name}
                    <a
                      href="#"
                      className="ml-2"
                      onClick={(e) => {
                        e.preventDefault();
                        handleJamTrackClose();
                      }}
                      style={{ fontSize: '1em', textDecoration: 'none' }}
                      title="Close JamTrack"
                    >
                      <FontAwesomeIcon icon="times" /> Close
                    </a>
                  </h5>
                  <JKSessionJamTrackStems
                    jamTrackStems={jamTrackStems}
                    mixerHelper={mixerHelper}
                  />
                </div>
              </>
            )}

          {/* Backing Track Section - Show track when player is open */}
          {showBackingTrackPlayer && mixerHelper.backingTracks && mixerHelper.backingTracks.length > 0 && (
            <>
              <div style={{ borderLeft: '1px solid #ddd', paddingLeft: '1rem' }}></div>
              <div className='backingTrack'>
                <h5>
                  Backing Track
                  <a
                    href="#"
                    className="ml-2"
                    onClick={(e) => {
                      e.preventDefault();
                      handleBackingTrackMainClose();
                    }}
                    style={{ fontSize: '1em', textDecoration: 'none' }}
                    title="Close Backing Track"
                  >
                    <FontAwesomeIcon icon="times" /> Close
                  </a>
                </h5>
                <JKSessionBackingTrack
                  backingTrack={mixerHelper.backingTracks[0]}
                  mixers={mixerHelper.backingTracks[0].mixers}
                  onClose={handleBackingTrackMainClose}
                />
              </div>
            </>
          )}

          {/* Metronome Section - Show track when metronome is open */}
          {metronomeState.isOpen && metronomeTrackMixers && metronomeTrackMixers.length > 0 && (
            <>
              <div style={{ borderLeft: '1px solid #ddd', paddingLeft: '1rem' }}></div>
              <div className='metronomeTrack'>
                <h5>
                  Metronome
                  <a
                    href="#"
                    className="ml-2"
                    onClick={(e) => {
                      e.preventDefault();
                      handleMetronomeClose();
                    }}
                    style={{ fontSize: '1em', textDecoration: 'none' }}
                    title="Close Metronome"
                  >
                    <FontAwesomeIcon icon="times" /> Close
                  </a>
                </h5>
                <JKSessionMetronome
                  mixers={metronomeTrackMixers[0]}
                  onClose={handleMetronomeClose}
                />
              </div>
            </>
          )}

        </div>

        {/* Connection Status Alerts */}
        {showConnectionAlert && (
          <div className='mt-4'>
            <Alert color={
              connectionStatus === ConnectionStatus.DISCONNECTED ? 'warning' :
                connectionStatus === ConnectionStatus.RECONNECTING ? 'info' :
                  connectionStatus === ConnectionStatus.ERROR ? 'danger' : 'success'
            }>
              <div className='d-flex align-items-center'>
                <div className='me-2'>
                  {connectionStatus === ConnectionStatus.DISCONNECTED && '⚠️'}
                  {connectionStatus === ConnectionStatus.RECONNECTING && '🔄'}
                  {connectionStatus === ConnectionStatus.ERROR && '❌'}
                  {connectionStatus === ConnectionStatus.CONNECTED && '✅'}
                </div>
                <div>
                  {connectionStatus === ConnectionStatus.DISCONNECTED && (
                    <strong>Connection Lost</strong>
                  )}
                  {connectionStatus === ConnectionStatus.RECONNECTING && (
                    <div>
                      <strong>Reconnecting...</strong>
                      {reconnectAttempts > 0 && (
                        <div className='small mt-1'>
                          Attempt {reconnectAttempts} of 10
                        </div>
                      )}
                    </div>
                  )}
                  {connectionStatus === ConnectionStatus.ERROR && (
                    <div>
                      <strong>Connection Failed</strong>
                      {lastError && (
                        <div className='small mt-1'>
                          {lastError.message || 'Unknown error'}
                        </div>
                      )}
                    </div>
                  )}
                  {connectionStatus === ConnectionStatus.CONNECTED && (
                    <strong>Reconnected Successfully</strong>
                  )}
                </div>
              </div>
            </Alert>
          </div>
        )}

        {/* Connection Status Badge in Header */}
        <div className='mt-3 mb-3'>
          <Badge
            color={
              connectionStatus === ConnectionStatus.CONNECTED ? 'success' :
                connectionStatus === ConnectionStatus.CONNECTING ? 'warning' :
                  connectionStatus === ConnectionStatus.RECONNECTING ? 'info' :
                    connectionStatus === ConnectionStatus.ERROR ? 'danger' : 'secondary'
            }
            className='me-2'
          >
            {connectionStatus === ConnectionStatus.CONNECTED && '🟢 Connected'}
            {connectionStatus === ConnectionStatus.CONNECTING && '🟡 Connecting...'}
            {connectionStatus === ConnectionStatus.RECONNECTING && '🔄 Reconnecting...'}
            {connectionStatus === ConnectionStatus.DISCONNECTED && '🟠 Disconnected'}
            {connectionStatus === ConnectionStatus.ERROR && '🔴 Error'}
          </Badge>
          {reconnectAttempts > 0 && (
            <Badge color='info' className='me-2'>
              Attempt {reconnectAttempts}/10
            </Badge>
          )}
        </div>

        {/* Debug Info */}
        {/* <div className='mt-4 p-3 bg-white rounded'>
          <h6>Debug Information</h6>
          <div className='row'>
            <div className='col-md-3'>
              <strong>Connection:</strong> {isConnected ? '✅ Connected' : '❌ Disconnected'}
            </div>
            <div className='col-md-3'>
              <strong>Status:</strong> {connectionStatus}
            </div>
          </div>
          <div className='row mt-2'>
            <div className='col-md-3'>
              <strong>Reconnect Attempts:</strong> {reconnectAttempts}
            </div>
            <div className='col-md-6'>
              <strong>Last Error:</strong> {lastError ? lastError.message : 'None'}
            </div>
          </div>
          <div className='row mt-2'>
            <div className='col-md-12'>
              <strong>Current Session:</strong> {JSON.stringify(currentSession)}
            </div>
          </div>
        </div> */}
      </CardBody>

      <JKSessionSettingsModal
        isOpen={showSettingsModal}
        toggle={() => dispatch(toggleModal('settings'))}
        currentSession={{ ...currentSession, privacy: musicianAccess }}
        loading={settingsLoading}
        onSave={async (payload) => {
          // console.log('Session settings :', payload);
          try {
            setSettingsLoading(true);

            switch (parseInt(payload.privacy)) {
              case SESSION_PRIVACY_MAP['public']:
                payload.musician_access = true;
                payload.approval_required = false;
                break;
              case SESSION_PRIVACY_MAP['private_approve']:
                payload.musician_access = true;
                payload.approval_required = true;
                break;
              case SESSION_PRIVACY_MAP['private_invite']:
                payload.musician_access = false;
                payload.approval_required = false;
                break;
              default:
                break;
            }

            const response = await updateSessionSettings({
              id: currentSessionIdRef.current,
              ...payload
            });
            const data = await response.json();
            // console.log('Updated session settings response:', data);
            dispatch(updateSessionData(data)); // Phase 4: dispatch to Redux
            dispatch(closeModal('settings'));
            toast.success('Session settings updated successfully');
          } catch (error) {
            // console.error('Error updating session settings:', error);
            toast.error('Failed to update session settings');
          } finally {
            setSettingsLoading(false);
          }

        }}
      />

      <JKSessionInviteModal
        currentSession={currentSession}
        show={showInviteModal}
        size="lg"
        onToggle={() => dispatch(closeModal('invite'))}
        friends={friends}
        initialInvitees={sessionInvitees}
        loading={inviteLoading}
        onSubmit={async (invitees) => {
          setSessionInvitees(invitees);
          // console.log('Submitted invitees:', invitees);
          const inviteeIds = invitees.map(i => i.id)
          const payload = {
            inviteeIds: inviteeIds.join()
          };
          try {
            setInviteLoading(true);
            const response = await updateSessionSettings({
              id: currentSessionIdRef.current,
              ...payload
            });
            const data = await response.json();
            // console.log('Updated session settings response:', data);
            dispatch(updateSessionData(data)); // Phase 4: dispatch to Redux
            dispatch(closeModal('invite'));
            toast.success('Invitations sent successfully');
          } catch (error) {
            // console.error('Error updating session settings:', error);
            toast.error('Failed to send invitations');
          } finally {
            setInviteLoading(false);
          }
        }}
      />

      <JKSessionVolumeModal
        isOpen={showVolumeModal}
        toggle={() => dispatch(toggleModal('volume'))}
      />

      <JKSessionRecordingModal
        isOpen={showRecordingModal}
        toggle={() => dispatch(toggleModal('recording'))}
        onSubmit={handleRecordingSubmit}
      />

      <JKSessionLeaveModal
        isOpen={showLeaveModal}
        toggle={() => dispatch(closeModal('leave'))} // Just close modal, don't navigate since session not left yet
        onSubmit={handleLeaveSubmit}
        loading={leaveLoading}
      />

      <UncontrolledTooltip target="audioInputsTooltip" trigger="hover click">
        <div>
          <p>Set the input level of your Audio Inputs for each of your tracks to a healthy level. It's important to set your input level correctly. If your level is set too high, you'll get distortion or clipping of your audio. If set too low, your audio signal will be too weak, which can cause noise and degrade your audio quality when you and others use the session mix to increase your volume in the mix.</p>
          <p>For instructions on how to set your Audio Input levels, read <a href="/help/audio-input-levels" target="_blank">this help article</a>.</p>
        </div>
      </UncontrolledTooltip>

      <UncontrolledTooltip target="sessionMixTooltip" trigger="hover click">
        <div>
          <p>Adjust the volume of each audio track (yours and others) in the Session Mix to get the mix where you want it (i.e. where it sounds good and well balanced to you). Any volume changes you make will affect only what you hear. They don’t affect the volume of what others hear in the session. Everyone has their own customizable session mix.</p>
          <p>Note that your session mix is the mix that will be used for any recordings you make and for any broadcasts you stream. If another musician in your session makes a recording or streams a broadcast, it will use that musician’s session mix, not yours.</p>
          <p>For instructions on how to set your Session Mix levels, <a href="https://jamkazam.freshdesk.com/support/solutions/articles/66000124834" target="_blank" rel="noopener noreferrer">read this help article</a>.</p>
        </div>
      </UncontrolledTooltip>

      {/* Backing Track Popup */}
      {showBackingTrackPopup && backingTrackData && (
        <WindowPortal
          onClose={handleBackingTrackClose}
          windowFeatures="width=500,height=250,left=200,top=200,menubar=no,toolbar=no,status=no,scrollbars=yes,resizable=yes,location=no, addressbar=no"
        >
          <JKSessionBackingTrackPlayer
            backingTrack={backingTrackData.backingTrack}
            jamClient={jamClient}
            session={backingTrackData.session}
            currentUser={backingTrackData.currentUser}
            isPopup={true}
            onClose={() => {
              // console.log('JKSessionScreen: JKSessionBackingTrackPlayer onClose called');
              dispatch(closeModal('backingTrack'));
              dispatch(clearBackingTrackData());
            }}
          />
        </WindowPortal>
      )}

      {/* Metronome Player Popup */}
      {metronomeState.isOpen && (
        <WindowPortal
          title="Metronome"
          onClose={handleMetronomeClose}
          windowFeatures="width=450,height=300,left=200,top=200,menubar=no,toolbar=no,status=no,scrollbars=yes,resizable=yes,location=no, addressbar=no"
          windowId="metronome-controls"
        >
          <JKSessionMetronomePlayer
            isOpen={metronomeState.isOpen}
            onClose={handleMetronomeClose}
            metronomeState={metronomeState}
            jamClient={jamClient}
            session={currentSession}
            currentUser={currentUser}
            isPopup={true}
          />
        </WindowPortal>
      )}

      {/* JamTrack Player Popup */}
      {showJamTrackPlayer && jamTrackData && (
        <WindowPortal
          onClose={handleJamTrackPlayerClose}
          windowFeatures="width=420,height=220,left=200,top=200,menubar=no,toolbar=no,status=no,scrollbars=no,resizable=yes,location=no,addressbar=no"
        >
          <JKSessionJamTrackPlayer
            jamTrack={jamTrackData.jamTrack}
            jamClient={jamClient}
            session={jamTrackData.session}
            currentUser={jamTrackData.currentUser}
            isPopup={true}
            onClose={handleJamTrackClose}
          />
        </WindowPortal>
      )}

      <JKSessionJamTrackModal
        isOpen={showJamTrackModal}
        toggle={() => dispatch(toggleModal('jamTrack'))}
        onJamTrackSelect={handleJamTrackSelect}
      />

      {/* Chat Window */}
      <JKSessionChatWindow />

      {/* Media Controls Popup - Only show when explicitly opened */}
      {showMediaControlsPopup && !popupGuard && (() => {
        // console.log('JKSessionScreen: RENDERING Media Controls Popup - showMediaControlsPopup:', showMediaControlsPopup, 'popupGuard:', popupGuard);
        setPopupGuard(true); // Set guard immediately to prevent re-renders
        return (
          <WindowPortal
            onClose={() => {
              // console.log('JKSessionScreen: Media Controls Popup closing');
              dispatch(closeModal('mediaControls'));
              setMediaControlsOpened(false);
              setPopupGuard(false); // Reset guard when closing
            }}
            windowFeatures="width=600,height=500,left=250,top=150,menubar=no,toolbar=no,status=no,scrollbars=yes,resizable=yes,location=no,addressbar=no"
            title="Media Controls"
            windowId="media-controls"
          >
            <JKPopupMediaControls onClose={() => {
              // console.log('JKSessionScreen: JKPopupMediaControls onClose called');
              dispatch(closeModal('mediaControls'));
              setMediaControlsOpened(false);
              setPopupGuard(false); // Reset guard when closing
            }} />
          </WindowPortal>
        );
      })()}
    </Card>
  )
}

export default memo(JKSessionScreen)
