import React, { createContext, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import dayjs from 'dayjs';

import { RECORD_MODES } from '@/modules/@common/app/constants';

import { getUserFirstNameSelector } from '@/modules/@common/user/redux/selectors';
import * as actions from '@/modules/uploaderNew/redux/slices';

const recordConfig = {
  mode: RECORD_MODES.SCREEN,
  mic: true,
  camera: false,
  micDeviceId: null,
  cameraDeviceId: null,
};

const captureMediaDevices = async (mediaConstraints) => {
  const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);

  return stream;
};

const captureScreen = async (
  mediaConstraints = {
    video: {
      cursor: 'always',
      resizeMode: 'crop-and-scale',
    },
  },
) => {
  const screenStream = await navigator.mediaDevices.getDisplayMedia(mediaConstraints);

  return screenStream;
};

const initRecorder = (recordType, stream, mic, setMediaBlob, setRecorder, setMediaStream, setTimer, props) => {
  const options = {};
  const chromeMimeType = 'video/webm;codec=vp8';

  if (MediaRecorder.isTypeSupported(chromeMimeType)) {
    options.mimeType = chromeMimeType;
  }

  const mediaRecorder = new MediaRecorder(stream, options);

  const chunks = [];
  let interval = null;
  let timer = 0;

  mediaRecorder.ondataavailable = (event) => {
    if (event.data.size > 0) {
      chunks.push(event.data);
    }
  };

  mediaRecorder.onstart = () => {
    interval = setInterval(() => {
      // eslint-disable-next-line no-plusplus
      setTimer(++timer);
    }, 1000);

    mediaRecorder.stream.getAudioTracks().forEach((track) => {
      // eslint-disable-next-line no-param-reassign
      track.enabled = !!mic;
    });
  };

  mediaRecorder.onpause = () => {
    clearInterval(interval);
  };

  mediaRecorder.onresume = () => {
    interval = setInterval(() => {
      // eslint-disable-next-line no-plusplus
      setTimer(++timer);
    }, 1000);
  };

  mediaRecorder.onstop = () => {
    clearInterval(interval);
    setTimer(0);

    const blob = new Blob(chunks, {
      type: 'video/webm',
    });

    const blobUrl = URL.createObjectURL(blob);

    setMediaBlob(blobUrl);

    const time = dayjs(new Date()).format('MMM D, YYYY hh:mm A');

    const videoFile = new File([blob], `${props.userName}'s ${recordType} Recording - ${time}.webm`);

    props.videoAction(videoFile);
    props.titleAction(videoFile.name.match(/(.+?)(\.[^.]*$|$)/)[1]);
    props.startUpload();

    setRecorder(null);
    setMediaStream(null);
  };

  setMediaStream(stream);
  setRecorder(mediaRecorder);
};

export const RecordingContext = createContext(undefined);

function RecordingProvider(props) {
  const dispatch = useDispatch();
  const [recorder, setRecorder] = useState(null);
  const [mediaStream, setMediaStream] = useState(null);
  const [mediaBlob, setMediaBlob] = useState(null);

  const [config, setConfig] = useState(recordConfig);
  const [isPaused, setPaused] = useState(false);
  const [mediaDevices, setMediaDevices] = useState([]);
  const [recordStep, setRecordStep] = useState(null);
  const [timer, setTimer] = useState(0);

  const userName = useSelector(getUserFirstNameSelector);

  const muteMicrophone = () => {
    if (recorder?.stream) {
      recorder.stream.getAudioTracks().forEach((track) => {
        // eslint-disable-next-line no-param-reassign
        track.enabled = !config.mic;
      });
      setConfig({
        ...config,
        mic: !config.mic,
      });
    }
  };

  const recordCamera = async ({ mic, micDeviceId, cameraDeviceId }, cancelCallback) => {
    let stream;

    try {
      stream = await captureMediaDevices({
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          sampleRate: 44100,
          deviceId: micDeviceId || undefined,
        },
        video: {
          deviceId: cameraDeviceId || undefined,
        },
      });

      initRecorder(RECORD_MODES.CAMERA, stream, mic, setMediaBlob, setRecorder, setMediaStream, setTimer, {
        userName,
        videoAction: (v) => dispatch(actions.videoAction(v)),
        titleAction: (v) => dispatch(actions.titleAction(v)),
        startUpload: (v) => dispatch(actions.startUploadAction(v)),
      });

      return stream;
    } catch {
      cancelCallback();
      return 'There is no access to camera';
    }
  };

  const recordScreen = async ({ mic, micDeviceId }, cancelCallback) => {
    let screenStream;
    try {
      screenStream = await captureScreen();

      if (screenStream) {
        let streams = [...screenStream.getTracks()];

        const audioStream = await captureMediaDevices({
          audio: {
            echoCancellation: true,
            noiseSuppression: true,
            sampleRate: 44100,
            deviceId: micDeviceId,
          },
          video: false,
        });

        if (audioStream) {
          streams = [...streams, ...audioStream.getTracks()];
        }

        const stream = new MediaStream([...streams]);

        initRecorder(RECORD_MODES.SCREEN, stream, mic, setMediaBlob, setRecorder, setMediaStream, setTimer, {
          userName,
          videoAction: (v) => dispatch(actions.videoAction(v)),
          titleAction: (v) => dispatch(actions.titleAction(v)),
          startUpload: (v) => dispatch(actions.startUploadAction(v)),
        });
      }

      return screenStream;
    } catch {
      cancelCallback();
      return 'There is no access to screen';
    }
  };

  const stopRecording = () => {
    if (recorder?.stream) {
      recorder.stream.getTracks().forEach((track) => track.stop());
    }
    if (recorder?.state !== 'inactive') {
      recorder?.stop();
    }
    if (mediaStream) {
      mediaStream.getTracks().forEach((track) => track.stop());
      setMediaStream(null);
    }
  };

  const recordingState = useMemo(
    () => ({
      mediaStream,
      mediaBlob,
      recorder,
      recordCamera,
      recordScreen,
      stopRecording,
      recordStep,
      setRecordStep,
      config,
      setConfig,
      isPaused,
      setPaused,
      mediaDevices,
      setMediaDevices,
      timer,
      muteMicrophone,
    }),
    [
      mediaStream,
      mediaBlob,
      recorder,
      recordCamera,
      recordScreen,
      stopRecording,
      recordStep,
      setRecordStep,
      config,
      setConfig,
      isPaused,
      setPaused,
      mediaDevices,
      setMediaDevices,
      timer,
      muteMicrophone,
    ],
  );

  return <RecordingContext.Provider value={recordingState}>{props.children}</RecordingContext.Provider>;
}

RecordingProvider.propTypes = {
  children: PropTypes.node.isRequired,
};

export default RecordingProvider;
