import {
  AIFeature,
  AIResult,
  Auth,
  ChatMessage,
  Configuration,
  ConnectionQuality,
  LocalParticipant,
  Participant,
  Recording,
  RemoteParticipant,
  RoomInfo,
  Session,
  SessionEvent,
  SessionState,
  StreamKind,
  StreamSource,
  Track,
  TranscriptionConfig,
  TranscriptionResult,
  VirtualBackgroundConfig,
  VirtualBackgroundType,
  ZocksSDK,
} from '@zocks-communications/client-sdk-js'
import { useZocksDispatch, useZocksStore } from '../../app/hooks'
import {
  activeSpeakersChanged,
  addRemoteParticipant,
  clearActiveSession,
  clearParticipants,
  clearRoomId,
  clearRoomName,
  connectionQualityChanged,
  participantLocalTrackPublished,
  participantLocalTrackUnPublished,
  participantTrackMuted,
  participantTrackPublished,
  participantTrackSubscribed,
  participantTrackUnMuted,
  participantTrackUnPublished,
  participantTrackUnsubscribed,
  removeRemoteParticipant,
  selectRecording,
  selectActiveSpeakers,
  selectIsConnecting,
  selectLocalParticipant,
  selectLocalParticipantIsConnected,
  selectRemoteParticipants,
  selectRoomId,
  selectRoomName,
  selectSessionState,
  selectTracksForParticipant,
  setActiveSession,
  setConnecting,
  setLocalParticipantId,
  setLocalParticipantName,
  setRecording,
  setRoomId,
  setRoomName,
  setSessionState,
  setSessionConfig,
  selectSessionConfig,
} from './sessionSlice'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ZocksConfig } from '../../models'
import {
  clearChatMessages,
  privateChatMessageReceived,
  privateChatMessageSent,
  publicChatMessageReceived,
  publicChatMessageSent,
} from '../chat/chatSlice'
import { ChatAttachmentModel } from '../chat'
import { getSessionByRoomId } from '../utils'
import {
  JoinOptions,
  ParticipantModel,
  ParticipantView,
  SessionView,
  TrackModel,
  ZocksActions,
  ZocksAuthActions,
} from './models'
import {
  clearCurrentVirtualBackground,
  setAvailableBackgrounds,
  setCurrentVirtualBackground,
} from '../virtualBackground/virtualBackgroundSlice'
import {
  clearRemoteParticipantTranscription,
  clearTranscription,
  setTranscriptionInProgress,
  setTranscriptionServiceEnabled,
  setTranscriptionServiceIsLoading,
  transcriptionReceived,
} from '../transcript/transcriptSlice'
import { aiResultReceived, clearAIFeatures, setAIFeaturesStatus } from '../aiFeatures/aiFeatureSlice'
import { AIFeaturesStatus } from '../aiFeatures'

const DEFAULT_SYNC_INTERVAL = 1000

export const useZocksActions = (zocksConfig: ZocksConfig): ZocksActions => {
  const appConfig = useMemo(() => {
    const _version = process.env.SDK_VERSION
    const appInfo = [`client-sdk-react/${_version} (Zocks)`]
      .concat(zocksConfig.appInfo ? [zocksConfig.appInfo] : [])
      .join(';')
    return {
      ...zocksConfig,
      appInfo,
    } as Configuration
  }, [zocksConfig])

  const zocksSDK = useMemo(() => ZocksSDK.init(appConfig), [appConfig])

  const dispatch = useZocksDispatch()

  const [session, setSession] = useState<Session>()

  const { synchronizeParticipants } = useSynchronizeParticipants()

  useEffect(() => {
    dispatch(setSessionConfig(appConfig))
  }, [appConfig, dispatch])

  useEffect(() => {
    if (zocksConfig.virtualBackgrounds) {
      dispatch(setAvailableBackgrounds(zocksConfig.virtualBackgrounds))
    }
  }, [zocksConfig.virtualBackgrounds, dispatch])

  useEffect(() => {
    const syncInterval = zocksConfig.syncInterval ?? DEFAULT_SYNC_INTERVAL
    if (session && syncInterval) {
      const interval = setInterval(() => {
        synchronizeParticipants()
      }, syncInterval)
      return () => clearInterval(interval)
    }
  }, [session, synchronizeParticipants, zocksConfig.syncInterval])

  const setupEventHandlers = (session: Session) => {
    session.on(SessionEvent.ParticipantLocalTrackPublished, (track: Track) => {
      if (track.isAvailable) {
        dispatch(
          participantLocalTrackPublished({
            trackId: track.id!,
            trackKind: track.kind,
            trackSource: track.source,
            trackIsMuted: track.isMuted,
          })
        )
      }
    })
    session.on(SessionEvent.ParticipantLocalTrackUnpublished, (track: Track) => {
      if (track.isAvailable) {
        dispatch(
          participantLocalTrackUnPublished({
            trackId: track.id!,
            trackKind: track.kind,
            trackSource: track.source,
          })
        )
      }
    })
    session.on(SessionEvent.ParticipantTrackPublished, (track: Track, participant: Participant) => {
      if (track.isAvailable) {
        dispatch(
          participantTrackPublished({
            participantId: participant.id,
            trackId: track.id,
            trackKind: track.kind,
            trackSource: track.source,
            trackIsMuted: track.isMuted,
            trackIsSubscribed: track.isSubscribed,
          })
        )
      }
    })
    session.on(SessionEvent.ParticipantTrackUnpublished, (track: Track, participant: Participant) => {
      if (track.isAvailable) {
        dispatch(
          participantTrackUnPublished({
            participantId: participant.id,
            trackId: track.id!,
            trackKind: track.kind,
            trackSource: track.source,
          })
        )
      }
    })
    session.on(SessionEvent.SessionConnected, () => {
      const {
        room: { id, name, roomInfo },
      } = session
      dispatch(setRoomId(id ?? ''))
      dispatch(setRoomName(name ?? ''))
      dispatch(setActiveSession(roomInfo?.activeSession ? { ...roomInfo.activeSession } : null))
    })
    session.on(SessionEvent.SessionStateChanged, (sessionState: SessionState) => {
      dispatch(setSessionState(sessionState))
      if (sessionState === SessionState.DISCONNECTED) {
        cleanUpState()
      }
    })
    session.on(SessionEvent.LocalParticipantConnected, (localParticipant: Participant) => {
      dispatch(setLocalParticipantId(localParticipant.id))
      dispatch(setLocalParticipantName(localParticipant.name))
    })
    session.on(SessionEvent.LocalParticipantReconnected, () => {
      synchronizeParticipants()
    })
    session.on(SessionEvent.ParticipantConnected, (participant: RemoteParticipant) => {
      dispatch(addRemoteParticipant({ participantId: participant.id, participantName: participant.name }))
    })
    session.on(SessionEvent.ParticipantDisconnected, (participantId: string | undefined) => {
      if (participantId) {
        dispatch(removeRemoteParticipant({ participantId }))
        dispatch(clearRemoteParticipantTranscription({ participantId }))
      }
    })
    session.on(SessionEvent.ParticipantTrackSubscribed, (track: Track, participant: RemoteParticipant) => {
      if (participant.id && track.isAvailable) {
        dispatch(
          participantTrackSubscribed({
            participantId: participant.id,
            trackId: track.id!,
            trackIsMuted: track.isMuted,
          })
        )
      }
    })
    session.on(SessionEvent.ParticipantTrackUnsubscribed, (track: Track, participant: RemoteParticipant) => {
      if (participant.id && track.isAvailable) {
        dispatch(participantTrackUnsubscribed({ participantId: participant.id, trackId: track.id! }))
      }
    })
    session.on(
      SessionEvent.ConnectionQualityChanged,
      (connectionQuality: ConnectionQuality, participant: LocalParticipant | RemoteParticipant) => {
        dispatch(connectionQualityChanged({ participantId: participant.id, connectionQuality }))
      }
    )
    session.on(SessionEvent.ParticipantTrackMuted, (track: Track, participant: Participant) => {
      dispatch(participantTrackMuted({ participantId: participant.id, trackId: track.id }))
    })
    session.on(SessionEvent.ParticipantTrackUnmuted, (track: Track, participant: Participant) => {
      dispatch(participantTrackUnMuted({ participantId: participant.id, trackId: track.id }))
    })
    session.on(SessionEvent.ActiveSpeakersChanged, (participants: Participant[]) => {
      dispatch(activeSpeakersChanged(participants.filter((p) => !!p.id).map((p) => p.id)))
    })

    session.on(SessionEvent.PrivateChatMessageReceived, (chatMessage: ChatMessage) => {
      const { message, timestamp, from, attachment } = chatMessage
      dispatch(
        privateChatMessageReceived({
          message,
          timestamp,
          from,
          to: {
            id: session.localParticipant.id,
            name: session.localParticipant.name!,
          },
          attachment,
        })
      )
    })
    session.on(SessionEvent.PublicChatMessageReceived, (chatMessage: ChatMessage) => {
      const { message, timestamp, from, attachment } = chatMessage

      dispatch(
        publicChatMessageReceived({
          message,
          timestamp,
          from,
          attachment,
        })
      )
    })

    session.on(SessionEvent.LocalTranscriptionEnabled, (autoEnable: boolean) => {
      dispatch(setTranscriptionServiceEnabled({ enabled: true, autoEnable: autoEnable }))
    })

    session.on(SessionEvent.LocalTranscriptionDisabled, () => {
      dispatch(setTranscriptionServiceEnabled({ enabled: false }))
    })

    session.on(SessionEvent.LocalTranscriptionStarted, () => {
      dispatch(setTranscriptionInProgress(true))
    })

    session.on(SessionEvent.LocalTranscriptionFinished, () => {
      dispatch(setTranscriptionInProgress(false))
    })

    session.on(
      SessionEvent.TranscriptionReceived,
      (transcriptionResult: TranscriptionResult, participant: Participant) => {
        dispatch(transcriptionReceived({ participantId: participant.id, transcriptionResult }))
      }
    )

    session.on(SessionEvent.AIResultReceived, (aiResult: AIResult) => {
      dispatch(aiResultReceived(aiResult))
    })

    session.on(SessionEvent.RecordingStarted, (recording: Recording) => {
      dispatch(setRecording(recording))
    })

    session.on(SessionEvent.RecordingFinished, () => {
      dispatch(setRecording(null))
    })
  }

  const cleanUpState = () => {
    dispatch(clearParticipants())
    dispatch(clearRoomId())
    dispatch(clearRoomName())
    dispatch(clearActiveSession())
    dispatch(clearChatMessages())
    dispatch(clearCurrentVirtualBackground())
    dispatch(clearTranscription())
  }

  const cleanUpStateOnJoin = () => {
    cleanUpState()
    dispatch(clearAIFeatures())
    dispatch(setAIFeaturesStatus(AIFeaturesStatus.INITIAL))
  }

  const createRoom = (): Promise<RoomInfo> => {
    return zocksSDK.API.media.createRoom()
  }

  const join = async (joinOptions: JoinOptions) => {
    cleanUpStateOnJoin()
    const {
      roomId,
      roomName,
      roomConfig,
      password,
      autoCreate,
      autoSubscribe,
      audioDeviceSettings,
      videoDeviceSettings,
      virtualBackground,
      virtualBackgroundConfig,
      afterJoinCallback,
      joinErrorCallback,
    } = joinOptions
    const zocksSession = zocksSDK.createNewSession()
    setSession(zocksSession)
    setupEventHandlers(zocksSession)

    try {
      dispatch(setConnecting(true))
      await zocksSession.join(roomId, autoCreate, { autoSubscribe }, roomConfig, password, roomName)

      afterJoinCallback && afterJoinCallback()
      if (audioDeviceSettings) {
        await zocksSession.localParticipant.publishAudio(audioDeviceSettings)
      }
      if (virtualBackground) {
        dispatch(setCurrentVirtualBackground({ type: virtualBackground }))
        await zocksSession.localParticipant.setVirtualBackground(
          virtualBackground ?? null,
          virtualBackgroundConfig,
          videoDeviceSettings
        )
      } else if (videoDeviceSettings) {
        await zocksSession.localParticipant.publishVideo(videoDeviceSettings)
      }
    } catch (e: any) {
      joinErrorCallback && joinErrorCallback(e)
    } finally {
      dispatch(setConnecting(false))
    }
  }

  const leave = () => {
    if (session?.localParticipant.hasVirtualBackground()) {
      session?.localParticipant.clearVirtualBackground(false)
    }
    session?.disconnect()
  }

  const publishVideo = useCallback(
    async (mediaTrackSettings?: MediaTrackSettings) => {
      await session?.localParticipant.publishVideo(mediaTrackSettings)
    },
    [session]
  )

  const unpublishVideo = useCallback(
    async (mediaTrackSettings?: MediaTrackSettings) => {
      await session?.localParticipant.unpublishVideo(mediaTrackSettings)
    },
    [session]
  )

  const publishAudio = useCallback(
    async (mediaTrackSettings?: MediaTrackSettings) => {
      await session?.localParticipant.publishAudio(mediaTrackSettings)
    },
    [session]
  )

  const unpublishAudio = useCallback(
    async (mediaTrackSettings?: MediaTrackSettings) => {
      await session?.localParticipant.unpublishAudio(mediaTrackSettings)
    },
    [session]
  )

  const publishScreenShare = useCallback(async () => {
    try {
      await session?.localParticipant.publishScreenShare()
    } catch (e) {
      console.error(e)
    }
  }, [session])

  const unpublishScreenShare = useCallback(async () => {
    await session?.localParticipant.unpublishScreenShare()
  }, [session])

  const switchActiveDevice = useCallback(
    async (kind: MediaDeviceKind, deviceId: string) => {
      await session?.switchActiveDevice(kind, deviceId)
    },
    [session]
  )

  const setVirtualBackground = useCallback(
    async (type: VirtualBackgroundType, config?: VirtualBackgroundConfig, mediaTrackSettings?: MediaTrackSettings) => {
      await session?.localParticipant.setVirtualBackground(type, config, mediaTrackSettings)
    },
    [session]
  )

  const clearVirtualBackground = useCallback(
    async (mediaTrackSettings?: MediaTrackSettings) => {
      await session?.localParticipant.clearVirtualBackground(true, mediaTrackSettings)
    },
    [session]
  )

  const sendPublicMessage = (message: string, attachment?: ChatAttachmentModel) => {
    if (!session) {
      throw Error('Cannot send public message without a session!')
    }
    session.sendMessageToRoom(message, attachment)
    dispatch(
      publicChatMessageSent({
        message,
        timestamp: Date.now(),
        from: {
          id: session.localParticipant.id,
          name: session.localParticipant.name!,
        },
        attachment,
      })
    )
  }

  const sendPrivateMessage = (
    message: string,
    remoteParticipant: ParticipantModel,
    attachment?: ChatAttachmentModel
  ) => {
    if (!remoteParticipant.id || !session) {
      throw Error('Cannot send private message without a session or recipient!')
    }
    session.sendMessageToParticipant(message, remoteParticipant.id, attachment)
    dispatch(
      privateChatMessageSent({
        message,
        timestamp: Date.now(),
        from: {
          id: session.localParticipant.id,
          name: session.localParticipant.name!,
        },
        to: {
          id: remoteParticipant.id,
          name: remoteParticipant.name!,
        },
        attachment,
      })
    )
  }

  const uploadAttachment = async (file: File, remoteParticipant?: ParticipantModel) => {
    if ((remoteParticipant && !remoteParticipant.id) || !session) {
      throw Error('Cannot upload attachment without a session or with empty recipient id!')
    }
    const recipientId = remoteParticipant?.id || undefined
    return session.uploadAttachment(file, recipientId)
  }

  const getAttachmentLocation = async (attachmentFileId: string) => {
    if (!session) {
      throw Error('Cannot get attachment location without a session!')
    }
    return session.getFileDownloadLocation(attachmentFileId)
  }

  const enableTranscription = async (config?: TranscriptionConfig) => {
    dispatch(setTranscriptionServiceIsLoading(true))
    try {
      await session?.transcriptionService.enable(config)
    } finally {
      dispatch(setTranscriptionServiceIsLoading(false))
    }
  }

  const disableTranscription = async () => {
    dispatch(setTranscriptionServiceIsLoading(true))
    try {
      await session?.transcriptionService.disable()
    } finally {
      dispatch(setTranscriptionServiceIsLoading(false))
    }
  }

  const startTranscription = async (audioConstraints?: MediaTrackConstraints) => {
    await session?.transcriptionService.startTranscription(audioConstraints)
  }

  const stopTranscription = async () => {
    await session?.transcriptionService.stopTranscription()
  }

  const listAIFeatures = async (): Promise<AIFeature[]> => {
    return (await session?.listAIFeatures()) ?? []
  }

  const listAIResults = async (): Promise<AIResult[]> => {
    return (await session?.listAIResults()) ?? []
  }

  const startRecording = async (): Promise<void> => {
    await session?.startRecording()
  }

  const stopRecording = async (): Promise<void> => {
    await session?.stopRecording()
  }

  return {
    session,
    sessionActions: {
      createRoom,
      join,
      leave,
    },
    localMediaActions: {
      publishVideo,
      publishAudio,
      unpublishVideo,
      unpublishAudio,
      publishScreenShare,
      unpublishScreenShare,
      switchActiveDevice,
      setVirtualBackground,
      clearVirtualBackground,
    },
    chatActions: {
      sendPublicMessage,
      sendPrivateMessage,
      uploadAttachment,
      getAttachmentLocation,
    },
    transcriptionActions: {
      enableTranscription,
      disableTranscription,
      startTranscription,
      stopTranscription,
    },
    aiActions: {
      listAIFeatures,
      listAIResults,
    },
    recordingActions: {
      startRecording,
      stopRecording,
    },
    mediaClient: zocksSDK.API.media,
    aiClient: zocksSDK.API.ai,
    transcriptionHTTPClient: zocksSDK.API.transcribeHttp,
    aiExecuteClient: zocksSDK.API.aiExecute,
    iamClient: zocksSDK.API.iam,
  }
}

export const useZocksAuth = (): ZocksAuthActions => {
  const sessionConfig = useZocksStore(selectSessionConfig)

  const auth = useMemo(
    () =>
      sessionConfig
        ? new Auth({
            accountId: sessionConfig.accountId,
            apiTokenProviders: sessionConfig.apiTokenProvider,
            apiTokenKeyName: sessionConfig.apiTokenKeyName,
          })
        : null,
    [sessionConfig]
  )

  const hasScope = useCallback(
    (resource: string, scope: string, superScopes?: string[]): boolean => {
      if (!auth) {
        return true
      }
      return auth.hasScope(resource, scope, superScopes)
    },
    [auth]
  )

  return {
    hasScope,
  }
}

export const useZocksSession = (): SessionView => {
  const roomId = useZocksStore(selectRoomId)
  const roomName = useZocksStore(selectRoomName)
  const sessionState = useZocksStore(selectSessionState)
  const isConnecting = useZocksStore(selectIsConnecting)
  const localParticipant = useZocksStore(selectLocalParticipant)
  const remoteParticipants = useZocksStore(selectRemoteParticipants)
  const localParticipantIsConnected = useZocksStore(selectLocalParticipantIsConnected)
  const recording = useZocksStore(selectRecording)

  return {
    roomId,
    roomName,
    sessionState,
    isConnecting,
    localParticipant,
    remoteParticipants,
    localParticipantIsConnected,
    recording,
  }
}

export const useParticipant = (participantModel: ParticipantModel): ParticipantView => {
  const tracks = useZocksStore((state) => selectTracksForParticipant(state, participantModel))
  const localParticipant = useZocksStore(selectLocalParticipant)
  const activeSpeakers = useZocksStore(selectActiveSpeakers)
  const isLocal = participantModel.id === localParticipant.id
  const isSpeaking = activeSpeakers.some((participantId) => participantId === participantModel.id)
  const isScreenSharing = !!tracks.screenShareTrack && tracks.screenShareTrack.isSubscribed

  const { id, name, connectionQuality } = participantModel

  return {
    id,
    name,
    tracks,
    isLocal,
    connectionQuality,
    isSpeaking,
    isScreenSharing,
  }
}

export const useLeaveOnUnload = (leave: () => void) => {
  useEffect(() => {
    window.onunload = () => {
      leave()
    }
  }, [leave])
}

export const useSynchronizeParticipants = () => {
  const dispatch = useZocksDispatch()
  const stateParticipants = useZocksStore(selectRemoteParticipants)
  const sessionId = useZocksStore(selectRoomId)
  const zocksSession = getSessionByRoomId(sessionId)

  const synchronizeParticipants = () => {
    if (!zocksSession) {
      return
    }
    const { participants: zocksParticipants } = zocksSession
    for (const stateParticipant of stateParticipants) {
      if (stateParticipant.id && !zocksParticipants.has(stateParticipant.id)) {
        console.warn(`Obsolete participant in React state, removing: ${stateParticipant.id}`)
        removeObsoleteParticipant(stateParticipant.id)
      }
    }

    for (const zocksParticipant of zocksParticipants.values()) {
      const { id: zocksParticipantId, name: zocksParticipantName, tracks: zocksParticipantTracks } = zocksParticipant
      const stateParticipant = stateParticipants.find((participant) => participant.id === zocksParticipantId)
      if (!stateParticipant) {
        console.warn(`Participant is missing from React state, adding: ${zocksParticipantId}`)
        addMissingParticipant(zocksParticipantTracks, zocksParticipantId, zocksParticipantName)
      } else {
        synchronizeExistingParticipant(stateParticipant, zocksParticipantTracks)
      }
    }
  }

  const removeObsoleteParticipant = (participantId: string) => {
    dispatch(removeRemoteParticipant({ participantId }))
  }

  const addMissingParticipant = (participantTracks: Track[], participantId: string, participantName?: string) => {
    dispatch(addRemoteParticipant({ participantId, participantName }))
    for (const zocksTrack of participantTracks) {
      addTrack(participantId, zocksTrack)
    }
  }

  const synchronizeExistingParticipant = (stateParticipant: ParticipantModel, zocksTracks: Track[]) => {
    fixMuteAndSubscriptionStatusOfTracks(stateParticipant, zocksTracks)
    addMissingTracks(stateParticipant, zocksTracks)
    removeObsoleteTracks(stateParticipant, zocksTracks)
  }

  const fixMuteAndSubscriptionStatusOfTracks = (stateParticipant: ParticipantModel, zocksTracks: Track[]) => {
    const {
      id: participantId,
      tracks: { videoTracks, audioTracks, screenShareTrack },
    } = stateParticipant
    if (participantId) {
      _fixMuteAndSubscriptionStatusOfTracks(participantId, videoTracks, zocksTracks)
      _fixMuteAndSubscriptionStatusOfTracks(participantId, audioTracks, zocksTracks)
      if (screenShareTrack) {
        _fixMuteAndSubscriptionStatusOfTracks(participantId, [screenShareTrack], zocksTracks)
      }
    }
  }

  const _fixMuteAndSubscriptionStatusOfTracks = (
    participantId: string,
    stateTracks: TrackModel[],
    zocksTracks: Track[]
  ) => {
    for (const stateTrack of stateTracks) {
      const zocksTrack = zocksTracks.find((t) => t.id === stateTrack.id)
      if (!zocksTrack) {
        continue
      }
      if (stateTrack.isSubscribed !== zocksTrack.isSubscribed) {
        console.warn(`Track ${zocksTrack.id} subscription status out of sync, 
          React: ${stateTrack.isSubscribed}, LiveKit: ${zocksTrack.isSubscribed}`)
        changeSubscribedStatusOfTrack(participantId, stateTrack.id, zocksTrack.isSubscribed, zocksTrack.isMuted)
      }
      if (stateTrack.isMuted !== zocksTrack.isMuted) {
        console.warn(`Track ${zocksTrack.id} mute status out of sync, 
          React: ${stateTrack.isMuted}, LiveKit: ${zocksTrack.isMuted}`)
        changeMuteStatusOfTrack(participantId, stateTrack.id, zocksTrack.isMuted)
      }
    }
  }

  const addMissingTracks = (stateParticipant: ParticipantModel, zocksParticipantTracks: Track[]) => {
    const {
      id: participantId,
      tracks: { videoTracks, audioTracks, screenShareTrack },
    } = stateParticipant

    for (const zocksTrack of zocksParticipantTracks) {
      const trackIsMissingFromState =
        !videoTracks.some((t) => t.id === zocksTrack.id) &&
        !audioTracks.some((t) => t.id === zocksTrack.id) &&
        screenShareTrack?.id !== zocksTrack.id
      if (trackIsMissingFromState) {
        console.warn(`Track is missing from React state, adding: ${zocksTrack.id}`)
        addTrack(participantId!, zocksTrack)
      }
    }
  }

  const removeObsoleteTracks = (stateParticipant: ParticipantModel, zocksTracks: Track[]) => {
    const {
      tracks: { videoTracks, audioTracks, screenShareTrack },
    } = stateParticipant
    for (const stateVideoTrack of videoTracks) {
      if (!zocksTracks.some((t) => t.id === stateVideoTrack.id)) {
        console.warn(`Obsolete track in React state, removing: ${stateVideoTrack.id}`)
        removeTrack(stateParticipant.id!, stateVideoTrack.id, StreamSource.CAMERA, StreamKind.VIDEO)
      }
    }
    for (const stateAudioTrack of audioTracks) {
      if (!zocksTracks.some((t) => t.id === stateAudioTrack.id)) {
        console.warn(`Obsolete track in React state, removing: ${stateAudioTrack.id}`)
        removeTrack(stateParticipant.id!, stateAudioTrack.id, StreamSource.MICROPHONE, StreamKind.AUDIO)
      }
    }
    if (screenShareTrack && !zocksTracks.some((t) => t.id === screenShareTrack.id)) {
      console.warn(`Obsolete track in React state, removing: ${screenShareTrack.id}`)
      removeTrack(stateParticipant.id!, screenShareTrack.id, StreamSource.SCREEN_SHARE, StreamKind.VIDEO)
    }
  }

  const addTrack = (participantId: string, track: Track) => {
    dispatch(
      participantTrackPublished({
        participantId: participantId,
        trackId: track.id,
        trackKind: track.kind,
        trackSource: track.source,
        trackIsMuted: track.isMuted,
        trackIsSubscribed: track.isSubscribed,
      })
    )
  }

  const removeTrack = (participantId: string, trackId: string, trackSource: StreamSource, trackKind: StreamKind) => {
    dispatch(participantTrackUnPublished({ participantId, trackId, trackSource, trackKind }))
  }

  const changeSubscribedStatusOfTrack = (
    participantId: string,
    trackId: string,
    shouldSubscribe: boolean,
    trackIsMuted: boolean
  ) => {
    if (shouldSubscribe) {
      dispatch(participantTrackSubscribed({ participantId: participantId, trackId: trackId, trackIsMuted }))
    } else {
      dispatch(participantTrackUnsubscribed({ participantId: participantId, trackId: trackId }))
    }
  }

  const changeMuteStatusOfTrack = (participantId: string, trackId: string, shouldMute: boolean) => {
    if (shouldMute) {
      dispatch(participantTrackMuted({ participantId, trackId }))
    } else {
      dispatch(participantTrackUnMuted({ participantId, trackId }))
    }
  }

  return { synchronizeParticipants }
}
