import { useCallback, useEffect, useRef, useState } from 'react'
import {
  createVirtualBackground,
  LocalMediaStream,
  MediaDeviceManager,
  StreamKind,
  StreamSource,
  VirtualBackground,
  VirtualBackgroundConfig,
  VirtualBackgroundType,
} from '@zocks-communications/client-sdk-js'
import { useZocksDispatch, useZocksStore } from '../../app/hooks'
import {
  clearCurrentVirtualBackground,
  selectAvailableVirtualBackgrounds,
  selectCurrentVirtualBackground,
  setCurrentVirtualBackground,
} from '../virtualBackground/virtualBackgroundSlice'
import { selectCurrentVideoDeviceId, useMediaDevice } from '../mediaDevice'
import { setSelectedMediaDevice, setVideoIsLoading } from '../mediaDevice/mediaDeviceSlice'
import { VirtualBackgroundItem } from '../virtualBackground'

export const useLocalMedia = (videoEnabledByDefault?: boolean, audioEnabledByDefault?: boolean) => {
  const dispatch = useZocksDispatch()
  const { currentAudioDeviceId, currentVideoDeviceId } = useMediaDevice()
  const [localAudioStream, setLocalAudioStream] = useState<LocalMediaStream>()
  const [localVideoStream, setLocalVideoStream] = useState<LocalMediaStream>()

  const mounted = useRef(true)

  useEffect(() => {
    mounted.current = true
    return () => {
      mounted.current = false
    }
  }, [])

  useEffect(() => {
    return () => {
      if (localVideoStream && !mounted.current) {
        localVideoStream.stopStream()
      }
    }
  }, [localVideoStream])

  useEffect(() => {
    return () => {
      if (localAudioStream && !mounted.current) {
        localAudioStream.stopStream()
      }
    }
  }, [localAudioStream])

  const iOSWorkaroundExecuted = useRef(false)

  const iOSWorkaround = useCallback(async () => {
    if (iOSWorkaroundExecuted.current) {
      return
    }
    iOSWorkaroundExecuted.current = true
    const isIOS =
      ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) ||
      // iPad on iOS 13 detection
      (navigator.userAgent.includes('Mac') && 'ontouchend' in document)
    if (isIOS) {
      const devices = await MediaDeviceManager.getDevices('videoinput')
      if (devices.length < 2) {
        return
      }
      const nonDefaultDevice = devices[1]
      const stream = await LocalMediaStream.create({ audio: false, video: { deviceId: nonDefaultDevice.deviceId } })
      stream.stopStream()
    }
  }, [])

  const setSelectedMediaDeviceId = useCallback(
    (stream: LocalMediaStream, kind: MediaDeviceKind) => {
      const deviceId = stream.mediaStreamTrack?.getSettings().deviceId
      if (deviceId) {
        dispatch(setSelectedMediaDevice({ kind, deviceId }))
      }
    },
    [dispatch]
  )

  const startLocalAudioStream = useCallback(
    (deviceId?: string) => {
      LocalMediaStream.create({ audio: { deviceId }, video: false }).then((audioStream) => {
        if (!mounted.current) {
          audioStream.stopStream()
        } else {
          setLocalAudioStream(audioStream)
          setSelectedMediaDeviceId(audioStream, 'audioinput')
        }
      })
    },
    [setSelectedMediaDeviceId]
  )

  const startLocalVideoStream = useCallback(
    (deviceId?: string) => {
      dispatch(setVideoIsLoading(true))
      iOSWorkaround().then(() => {
        LocalMediaStream.create({ audio: false, video: { deviceId } })
          .then((videoStream) => {
            if (!mounted.current) {
              videoStream.stopStream()
            } else {
              setLocalVideoStream(videoStream)
              setSelectedMediaDeviceId(videoStream, 'videoinput')
            }
          })
          .finally(() => dispatch(setVideoIsLoading(false)))
      })
    },
    [dispatch, setSelectedMediaDeviceId, iOSWorkaround]
  )

  const toggleLocalAudioStream = () => {
    if (localAudioStream) {
      localAudioStream.stopStream()
      setLocalAudioStream(undefined)
    } else {
      startLocalAudioStream(currentAudioDeviceId || undefined)
    }
  }

  const toggleLocalVideoStream = () => {
    if (localVideoStream) {
      localVideoStream.stopStream()
      setLocalVideoStream(undefined)
    } else {
      startLocalVideoStream(currentVideoDeviceId || undefined)
    }
  }

  const selectAudioDevice = (audioDevice: MediaDeviceInfo) => {
    if (localAudioStream) {
      if (localAudioStream.mediaStreamTrack?.getSettings().deviceId === audioDevice.deviceId) {
        return
      }
      localAudioStream.stopStream()
      startLocalAudioStream(audioDevice.deviceId)
    }
  }

  const selectVideoDevice = (videoDevice: MediaDeviceInfo) => {
    if (localVideoStream) {
      if (localVideoStream.mediaStreamTrack?.getSettings().deviceId === videoDevice.deviceId) {
        return
      }
      localVideoStream.stopStream()
      setLocalVideoStream(undefined)
      startLocalVideoStream(videoDevice.deviceId)
    }
  }

  useEffect(() => {
    if (videoEnabledByDefault) {
      startLocalVideoStream()
    }
  }, [videoEnabledByDefault, startLocalVideoStream])

  useEffect(() => {
    if (audioEnabledByDefault) {
      startLocalAudioStream()
    }
  }, [audioEnabledByDefault, startLocalAudioStream])

  return {
    currentAudioDeviceId,
    currentVideoDeviceId,
    localAudioStream,
    localVideoStream,
    toggleLocalAudioStream,
    toggleLocalVideoStream,
    selectAudioDevice,
    selectVideoDevice,
  }
}

export const useLocalVirtualBackgrounds = (defaultVirtualBackground?: VirtualBackgroundItem) => {
  const dispatch = useZocksDispatch()
  const currentVirtualBackground = useZocksStore(selectCurrentVirtualBackground)
  const availableVirtualBackgrounds = useZocksStore(selectAvailableVirtualBackgrounds)
  const currentVideoDeviceId = useZocksStore(selectCurrentVideoDeviceId)

  const [virtualBackground, setVirtualBackground] = useState<VirtualBackground>()
  const [virtualBackgroundStream, setVirtualBackgroundStream] = useState<LocalMediaStream>()
  const [defaultVirtualBackgroundStarted, setDefaultVirtualBackgroundStarted] = useState<boolean>(false)

  const createNewVirtualBackground = useCallback(
    async ({
      virtualBackgroundType,
      deviceId,
      config,
    }: {
      virtualBackgroundType: VirtualBackgroundType
      deviceId?: string
      config?: VirtualBackgroundConfig
    }) => {
      await virtualBackground?.stop()
      const sourceVideoStream = await LocalMediaStream.create({
        audio: false,
        video: { deviceId, width: { ideal: 1280 }, height: { ideal: 720 } },
      })
      const newVirtualBackground = createVirtualBackground(
        virtualBackgroundType,
        sourceVideoStream.mediaStream!,
        config
      )
      await newVirtualBackground.start()
      const targetVideoStream = LocalMediaStream.createFromMediaStreamTrack(
        newVirtualBackground.outputStream,
        newVirtualBackground.outputStreamTrack,
        StreamKind.VIDEO,
        StreamSource.CAMERA
      )
      setVirtualBackgroundStream(targetVideoStream)
      setVirtualBackground(newVirtualBackground)
    },
    [virtualBackground]
  )

  const stopLocalVirtualBackground = async () => {
    if (!virtualBackground) {
      return
    }
    await stopVirtualBackground()
  }

  const stopVirtualBackground = useCallback(async () => {
    await virtualBackground?.stop()
    setVirtualBackgroundStream(undefined)
    setVirtualBackground(undefined)
  }, [virtualBackground])

  const changeVirtualBackground = useCallback(
    async (
      virtualBackgroundType: VirtualBackgroundType | null,
      deviceId?: string,
      config?: VirtualBackgroundConfig
    ) => {
      try {
        dispatch(setVideoIsLoading(true))
        if (virtualBackgroundType) {
          await createNewVirtualBackground({ virtualBackgroundType, deviceId, config })
          dispatch(setCurrentVirtualBackground({ type: virtualBackgroundType, url: config?.backgroundImageUrl }))
        } else {
          await stopVirtualBackground()
          dispatch(clearCurrentVirtualBackground())
        }
      } finally {
        dispatch(setVideoIsLoading(false))
      }
    },
    [createNewVirtualBackground, dispatch, stopVirtualBackground]
  )

  useEffect(() => {
    if (defaultVirtualBackground && currentVideoDeviceId && !defaultVirtualBackgroundStarted) {
      changeVirtualBackground(defaultVirtualBackground.type, currentVideoDeviceId, {
        backgroundImageUrl: defaultVirtualBackground.url,
      }).then(() => setDefaultVirtualBackgroundStarted(true))
    }
  }, [defaultVirtualBackground, defaultVirtualBackgroundStarted, currentVideoDeviceId, changeVirtualBackground])

  return {
    availableVirtualBackgrounds,
    currentVirtualBackground,
    virtualBackgroundStream,
    changeVirtualBackground,
    stopLocalVirtualBackground,
  }
}
