import { AzureCommunicationTokenCredential } from '@azure/communication-common';
import { CallClient, LocalVideoStream, VideoStreamRenderer } from '@azure/communication-calling';

class VideoCall {
  constructor() {
    this.callClient = null;
    this.callAgent = null;
    this.deviceManager = null;
    this.localVideoStream = null;
    this.call = null;
    this.videoElement = document.createElement('video');
    this.videoElement.setAttribute('autoplay', '');
    this.videoElement.setAttribute('muted', '');
    this.stateChangedCallback = null;
    this.remoteParticipantsUpdated = null;
    this.isLocalVideoStartedChanged = null;
    this.localVideoStreamsUpdated = null;
    this.idChanged = null;
    this.onCreateLocalVideoStream = null;
    this.remoteParticipantStateChanged = null;
    this.remoteVideoIsAvailableChanged = null;
  }

  async init(userAccessToken, options) {
    this.stateChangedCallback = options.stateChangedCallback;
    this.remoteParticipantsUpdated = options.remoteParticipantsUpdated;
    this.isLocalVideoStartedChanged = options.isLocalVideoStartedChanged;
    this.localVideoStreamsUpdated = options.localVideoStreamsUpdated;
    this.idChanged = options.idChanged;
    this.onCreateLocalVideoStream = options.onCreateLocalVideoStream;
    this.remoteParticipantStateChanged = options.remoteParticipantStateChanged;
    this.remoteVideoIsAvailableChanged = options.remoteVideoIsAvailableChanged;

    const tokenCredential = new AzureCommunicationTokenCredential(userAccessToken);

    this.callClient = new CallClient();
    this.callAgent = await this.callClient.createCallAgent(tokenCredential);

    this.deviceManager = await this.callClient.getDeviceManager();
    await this.deviceManager.askDevicePermission({ audio: true, video: true });
    const cameras = await this.deviceManager.getCameras();
    this.localVideoStream = new LocalVideoStream(cameras[0]);
  }

  async startLocalVideo() {
    if (this.localVideoStream) {
      //const localVideoOptions = new VideoStreamOptions();
      await this.callAgent.startCall([this.callAgent.identity], { video: [this.localVideoStream] });
    }
  }

  stopLocalVideo() {
    if (this.call) {
      this.call.stopVideo(this.localVideoStream);
    }
  }

  async joinRoom(roomId) {
    // Assuming you have a room ID and call the appropriate ACS API to join a room
    // This is just a placeholder, replace with actual logic
    console.log('Joining room:', roomId);
    if (this.callAgent) {
      try {
        const localVideoStream = await this.createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;

        const roomCallLocator = { roomId: roomId };
        let call = this.callAgent.join(roomCallLocator, { videoOptions });
        this.subscribeToCall(call);
      } catch (error) {
        console.error(error);
      }
    }
  }

  async subscribeToCall(call) {
    try {
      call.on('idChanged', () => {
        this.idChanged?.(call.id);
      });
      call.on('stateChanged', async () => {
        this.stateChangedCallback?.(call.state);
      });
      call.on('isLocalVideoStartedChanged', () => {
        this.isLocalVideoStartedChanged?.(call.isLocalVideoStarted);
      });
      call.on('localVideoStreamsUpdated', e => {
        this.localVideoStreamsUpdated?.(e);
      });

      // Subscribe to the call's 'remoteParticipantsUpdated' event to be
      // notified when new participants are added to the call or removed from the call.
      call.on('remoteParticipantsUpdated', e => {
        this.remoteParticipantsUpdated?.(e);

        e.added.forEach(remoteParticipant => {
          this.subscribeToRemoteParticipant(remoteParticipant)
        });
        // Unsubscribe from participants that are removed from the call
        e.removed.forEach(remoteParticipant => {
          console.log('Remote participant removed from the call.');
        });
      });

      call.localVideoStreams.forEach(async (lvs) => {
        this.localVideoStream = lvs;
        await this.displayLocalVideoStream(lvs);
      });

      // Inspect the call's current remote participants and subscribe to them.
      call.remoteParticipants.forEach(remoteParticipant => {
        this.subscribeToRemoteParticipant(remoteParticipant);
      });
    } catch (error) {
      console.error(error);
    }
  }

  subscribeToRemoteParticipant(remoteParticipant) {
    try {
      // Inspect the initial remoteParticipant.state value.
      console.log(`Remote participant state: ${remoteParticipant.state}`);
      // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
      remoteParticipant.on('stateChanged', () => {
        console.log(`Remote participant state changed: ${remoteParticipant.state}`, JSON.stringify(remoteParticipant));
        this.remoteParticipantStateChanged?.(remoteParticipant.state);
      });

      // Inspect the remoteParticipants's current videoStreams and subscribe to them.
      remoteParticipant.videoStreams.forEach(remoteVideoStream => {
        this.subscribeToRemoteVideoStream(remoteVideoStream);
      });
      // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
      // notified when the remoteParticipant adds new videoStreams and removes video streams.
      remoteParticipant.on('videoStreamsUpdated', e => {
        // Subscribe to new remote participant's video streams that were added.
        e.added.forEach(remoteVideoStream => {
          this.subscribeToRemoteVideoStream(remoteVideoStream);
        });
        // Unsubscribe from remote participant's video streams that were removed.
        e.removed.forEach(remoteVideoStream => {
          console.log('Remote participant video stream was removed.');
        })
      });
    } catch (error) {
      console.error(error);
    }
  }

  async subscribeToRemoteVideoStream(remoteVideoStream) {
    let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;

    const createView = async () => {
      // Create a renderer view for the remote video stream.
      view = await renderer.createView();
      this.remoteVideoIsAvailableChanged?.({
        isAvailable: remoteVideoStream.isAvailable,
        participantId: remoteVideoStream.tsParticipantId,
        el: view.target
      });
    }

    // Remote participant has switched video on/off
    remoteVideoStream.on('isAvailableChanged', async () => {
      try {
        if (remoteVideoStream.isAvailable) {
          await createView();
        } else {
          view?.dispose();
        }
      } catch (e) {
        console.error(e);
      }
    });

    // Remote participant has video on initially.
    if (remoteVideoStream.isAvailable) {
      try {
        await createView();
      } catch (e) {
        console.error(e);
      }
    }
  }

  async createLocalVideoStream() {
    const camera = (await this.deviceManager.getCameras())[0];
    if (camera) {
      return new LocalVideoStream(camera);
    } else {
      console.error(`No camera device found on the system`);
    }
  }

  leaveRoom() {
    if (this.call) {
      this.call.hangUp();
    }
  }

  async displayLocalVideoStream(lvs) {
    try {
      let localVideoStreamRenderer = new VideoStreamRenderer(lvs);
      const view = await localVideoStreamRenderer.createView();
      this.onCreateLocalVideoStream?.(view.target);
    } catch (error) {
      console.error(error);
    }
  }
}

export default VideoCall;