/*
Copyright (C) 2024 3NSoft Inc.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { defineStore } from 'pinia';
import { PeerChannelWithStreams } from '@video/services/streaming-channel';

export interface PeerState {
  peerAddr: string;
  peerName: string;
  ui: PeerUiState;
  webRTCConnected: boolean;
  channel: PeerChannelWithStreams;
}

export interface StreamInfo {
  streamId: string;
  type: StreamType;
}

export type StreamType = 'camera+mic' | 'window' | 'screen';

export interface PeerUiState {
  isMicOn: boolean;
  isCamOn: boolean;
  streams: StreamInfo[];
}

export interface StreamsStoreState {
  chatName: string;
  ownName: string;
  ownAddr: string;
  ownVideoDeviceId: string | null;
  ownVAStream: MediaStream | null;
  isMicOn: boolean;
  isCamOn: boolean;
  peers: PeerState[];
}

export function makePeerState(
  peerAddr: string, peerName: string,
  channel: PeerChannelWithStreams
): PeerState {
  return {
    peerAddr,
    peerName,
    ui: {
      isCamOn: false,
      isMicOn: false,
      streams: []
    },
    webRTCConnected: false,
    channel
  };
}

export const useStreamsStore = defineStore('streams', {

  state: () => ({
    peers: [],
    chatName: '',
    ownName: '',
    ownAddr: '',
    isMicOn: false,
    isCamOn: false,
    ownVideoDeviceId: null,
    ownVAStream: null,
  } as StreamsStoreState),

  getters: {
    isGroupChat: state => (state.peers.length > 1),

    isAnyOneConnected: state => !!state.peers.find(p => p.webRTCConnected),

    singlePeer: state => {
      if (state.peers.length !== 1) {
        throw new Error(`This is a group chat. Use address based access`);
      }
      return state.peers[0];
    },

    singlePeerUiState: state => {
      if (state.peers.length !== 1) {
        throw new Error(`This is a group chat. Use address based access`);
      }
      return state.peers[0].ui;
    },

    isOwnAudioAvailable: ({ ownVAStream }) => (ownVAStream ?
      (ownVAStream.getAudioTracks().length > 0) :
      undefined
    ),

    isOwnVideoAvailable: ({ ownVAStream }) => (ownVAStream ?
      (ownVAStream.getVideoTracks().length > 0) :
      undefined
    ),
  },

  actions: {
    setMicOn(val: boolean) {
      if ((this.isMicOn !== val) && this.ownVAStream) {
        this.isMicOn = val;
        this.ownVAStream.getAudioTracks().forEach(audioTrack => {
          audioTrack.enabled = val;
        });
        const streamId = this.ownVAStream.id;
        this.peers.forEach(({ channel }) => channel.signalOwnStreamState(
          streamId, val, this.isCamOn
        ));
      }
    },

    setCamOn(val: boolean) {
      if ((this.isCamOn !== val) && this.ownVAStream) {
        this.isCamOn = val;
        this.ownVAStream.getVideoTracks().forEach(videoTrack => {
          videoTrack.enabled = val;
        });
        const streamId = this.ownVAStream.id;
        this.peers.forEach(({ channel }) => channel.signalOwnStreamState(
          streamId, this.isMicOn, this.isCamOn
        ));
      }
    },

    setOwnVAStream(val: MediaStream | null, videoDevId: string | null) {
      this.ownVideoDeviceId = videoDevId;
      this.ownVAStream = val;
      this.ownVAStream?.getAudioTracks().forEach(audioTrack => {
        audioTrack.enabled = this.isMicOn;
      });
      this.ownVAStream?.getVideoTracks().forEach(videoTrack => {
        videoTrack.enabled = this.isCamOn;
      });
    },

    getPeer(peerAddr: string) {
      const peer = this.peers.find(p => (p.peerAddr === peerAddr));
      if (!peer) {
        throw new Error(`Peer with address ${peerAddr} not found among ${this.peers.length} peers. Is it exact spelling as is used in chat info?`);
      }
      return peer;
    },

    getStream(peerAddr: string, streamId: string) {
      const { channel, ui } = this.getPeer(peerAddr);
      const stream = channel.getStream(streamId);
      if (stream) {
        const { type } = ui.streams.find(s => (s.streamId === streamId))!;
        return { stream, type };
      }
    },

    hasTrackON(
      peerAddr: string, streamType: StreamType, trackType: 'audio'|'video'
    ): boolean {
      const { ui, channel } = this.getPeer(peerAddr);
      for (const { streamId, type } of ui.streams) {
        if (type !== streamType) {
          continue;
        }
        const stream = channel.getStream(streamId);
        if (!stream) {
          continue;
        }
        if (trackType === 'audio') {
          if (stream.getAudioTracks().find(t => t.enabled)) {
            return true;
          }
        } else if (trackType === 'video') {
          if (stream.getVideoTracks().find(t => t.enabled)) {
            return true;
          }
        }
      }
      return false;
    },

    syncUIWithTracksState(peerAddr: string): void {
      const { ui } = this.getPeer(peerAddr);
      ui.isMicOn = this.hasTrackON(peerAddr, 'camera+mic', 'audio');
      ui.isCamOn = this.hasTrackON(peerAddr, 'camera+mic', 'video');
      console.log(`🧐 peer ui synced with`, { isMicOn: ui.isMicOn, isCamOn: ui.isCamOn });
    },

  },
});

export type StreamsStore = ReturnType<typeof useStreamsStore>;
export type Peer = ReturnType<StreamsStore['getPeer']>;
