/*
 * https://github.com/morethanwords/tweb
 * Copyright (C) 2019-2021 Eduard Kuzmenko
 * https://github.com/morethanwords/tweb/blob/master/LICENSE
 */

import PopupElement from "../popups";
import { hexToRgb } from "../../helpers/color";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
import customProperties from "../../helpers/dom/customProperties";
import { GroupCall, GroupCallParticipant } from "../../layer";
import type { AppChatsManager } from "../../lib/appManagers/appChatsManager";
import type { AppGroupCallsManager } from "../../lib/appManagers/appGroupCallsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import GROUP_CALL_STATE from "../../lib/calls/groupCallState";
import { RLottieColor } from "../../lib/rlottie/rlottiePlayer";
import rootScope from "../../lib/rootScope";
import ButtonIcon from "../buttonIcon";
import GroupCallMicrophoneIcon from "./microphoneIcon";
import GroupCallParticipantsElement from "./participants";
import GroupCallParticipantsVideoElement from "./participantVideos";
import PopupPeer from "../popups/peer";
import GroupCallDescriptionElement from "./description";
import GroupCallTitleElement from "./title";
import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../../helpers/dom/fullScreen";
import Scrollable from "../scrollable";
import { MovableState } from "../movableElement";
import animationIntersector from "../animationIntersector";
import { IS_APPLE_MOBILE } from "../../environment/userAgent";
import throttle from "../../helpers/schedulers/throttle";
import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport";
import GroupCallInstance from "../../lib/calls/groupCallInstance";
import makeButton from "../call/button";
import MovablePanel from "../../helpers/movablePanel";
import findUpClassName from "../../helpers/dom/findUpClassName";
import safeAssign from "../../helpers/object/safeAssign";
import toggleClassName from "../../helpers/toggleClassName";
import { AppManagers } from "../../lib/appManagers/managers";
import themeController from "../../helpers/themeController";
import groupCallsController from "../../lib/calls/groupCallsController";

export enum GROUP_CALL_PARTICIPANT_MUTED_STATE {
  UNMUTED,
  MUTED,
  MUTED_FOR_ME,
  MUTED_BY_ADMIN,
  HAND
}

export type GROUP_CALL_PARTICIPANT_CLEARED_MUTED_STATE = Exclude<GROUP_CALL_PARTICIPANT_MUTED_STATE, GROUP_CALL_PARTICIPANT_MUTED_STATE.MUTED_BY_ADMIN | GROUP_CALL_PARTICIPANT_MUTED_STATE.MUTED_FOR_ME>;

export function getGroupCallParticipantMutedState(participant: GroupCallParticipant) {
  const states = GROUP_CALL_PARTICIPANT_MUTED_STATE;
  if(participant.pFlags.muted_by_you) {
    return states.MUTED_FOR_ME;
  } else if(participant.raise_hand_rating !== undefined) {
    return states.HAND;
  } else if(participant.pFlags.muted) {
    return participant.pFlags.can_self_unmute ? states.MUTED : states.MUTED_BY_ADMIN;
  } else {
    return states.UNMUTED;
  }
}

export function clearMutedStateModifier(state: GROUP_CALL_PARTICIPANT_MUTED_STATE): GROUP_CALL_PARTICIPANT_CLEARED_MUTED_STATE {
  const states = GROUP_CALL_PARTICIPANT_MUTED_STATE;
  switch(state) {
    case states.MUTED_BY_ADMIN:
    case states.MUTED_FOR_ME:
      return states.MUTED;
    default:
      return state;
  }
}

export function getColorByMutedState(state: GROUP_CALL_PARTICIPANT_MUTED_STATE) {
  const states = GROUP_CALL_PARTICIPANT_MUTED_STATE;
  let color: RLottieColor, colorStr: 'blue' | 'green' | 'secondary' | 'red';
  switch(state) {
    case states.HAND:
      colorStr = 'blue';
      break;
    case states.MUTED:
    case states.MUTED_FOR_ME:
    case states.MUTED_BY_ADMIN:
      colorStr = state === states.MUTED ? 'secondary' : 'red';
      break;
    case states.UNMUTED:
      colorStr = 'green';
      break;
  }

  const propertyValue = customProperties.getProperty('gc-' + colorStr + '-text-color');
  color = hexToRgb(propertyValue);

  return color;
}

export enum GROUP_CALL_MICROPHONE_BUTTON_STATE {
  HAND,
  MUTED,
  UNMUTED,
}

export function getGroupCallMicrophoneButtonState(groupCall: GroupCall.groupCall, participant: GroupCallParticipant) {
  const states = GROUP_CALL_MICROPHONE_BUTTON_STATE;
  if(!participant.pFlags.can_self_unmute) {
    return states.HAND;
  } else if(participant.pFlags.muted) {
    return states.MUTED
  } else {
    return states.UNMUTED;
  }
}

let previousState: MovableState = {
  width: 420,
  height: 640
};

const className = 'group-call';

export default class PopupGroupCall extends PopupElement {
  private instance: GroupCallInstance;
  private groupCallTitle: GroupCallTitleElement;
  private groupCallDescription: GroupCallDescriptionElement;
  private groupCallBodyHeaderDescription: GroupCallDescriptionElement;
  private groupCallParticipants: GroupCallParticipantsElement;
  private groupCallParticipantsVideo: GroupCallParticipantsVideoElement;
  private groupCallMicrophoneIcon: GroupCallMicrophoneIcon;
  private videosCount: number;
  private btnFullScreen: HTMLButtonElement;
  private btnExitFullScreen: HTMLButtonElement;
  private btnInvite: HTMLButtonElement;
  private btnShowColumn: HTMLButtonElement;
  private movablePanel: MovablePanel;
  private buttonsContainer: HTMLDivElement;
  private btnFullScreen2: HTMLButtonElement;
  private btnVideo: HTMLDivElement;
  private btnScreen: HTMLDivElement;

  constructor() {
    super('popup-group-call', undefined, {
      body: true,
      withoutOverlay: true,
      closable: true
    });

    this.videosCount = 0;
    this.container.classList.add(className, 'night');

    const instance = this.instance = groupCallsController.groupCall;
    const {listenerSetter} = this;

    if(!IS_APPLE_MOBILE) {
      const btnFullScreen = this.btnFullScreen = ButtonIcon('fullscreen');
      const btnFullScreen2 = this.btnFullScreen2 = ButtonIcon('fullscreen ' + className + '-cfs');
      const btnExitFullScreen = this.btnExitFullScreen = ButtonIcon('smallscreen');
  
      attachClickEvent(btnFullScreen, this.onFullScreenClick, {listenerSetter});
      attachClickEvent(btnFullScreen2, this.onFullScreenClick, {listenerSetter});
  
      attachClickEvent(btnExitFullScreen, () => {
        cancelFullScreen();
      }, {listenerSetter});
  
      addFullScreenListener(this.container, this.onFullScreenChange, listenerSetter);
    }

    const btnInvite = this.btnInvite = ButtonIcon('adduser');
    const btnShowColumn = this.btnShowColumn = ButtonIcon('rightpanel ' + className + '-only-big');

    attachClickEvent(btnShowColumn, this.toggleRightColumn, {listenerSetter});

    const headerInfo = document.createElement('div');
    headerInfo.classList.add(className + '-header-info');

    this.title.classList.add(className + '-header-title');

    const subtitle = document.createElement('div');
    subtitle.classList.add(className + '-header-subtitle');

    headerInfo.append(this.title, subtitle);

    this.header.classList.add(className + '-header');
    this.header.append(...[this.btnExitFullScreen, headerInfo/* , btnInvite */, this.btnFullScreen, btnShowColumn].filter(Boolean));

    const newHeader = this.header.cloneNode(false) as HTMLElement;
    const newHeaderInfo = headerInfo.cloneNode(false) as HTMLElement;
    const newHeaderTitle = this.title.cloneNode(false) as HTMLElement;

    newHeaderInfo.append(newHeaderTitle);

    const btnHideColumn = ButtonIcon('rightpanel');
    newHeader.append(...[btnHideColumn, newHeaderInfo, this.btnFullScreen2].filter(Boolean));

    attachClickEvent(btnHideColumn, this.toggleRightColumn, {listenerSetter});

    this.body.prepend(newHeader);

    const videosScrollable = new Scrollable(undefined);
    videosScrollable.container.classList.add('group-call-big-video-container');
    this.container.append(videosScrollable.container);

    this.groupCallTitle = new GroupCallTitleElement(this.title);
    this.groupCallDescription = new GroupCallDescriptionElement(subtitle);
    this.groupCallBodyHeaderDescription = new GroupCallDescriptionElement(newHeaderTitle);
    this.constructButtons();

    this.groupCallParticipantsVideo = new GroupCallParticipantsVideoElement({
      appendTo: videosScrollable.container,
      instance,
      listenerSetter,
      displayPinned: true,
      onLengthChange: (length) => {
        this.videosCount = length;
        this.toggleBigLayout();
      },
      managers: this.managers
    });
    this.groupCallParticipants = new GroupCallParticipantsElement({
      appendTo: this.body,
      instance,
      listenerSetter,
      managers: this.managers
    });

    this.movablePanel = new MovablePanel({
      listenerSetter,
      movableOptions: {
        minWidth: 400,
        minHeight: 480,
        element: this.element,
        verifyTouchTarget: (e) => {
          const target = e.target;
          if(findUpClassName(target, 'chatlist') || 
            findUpClassName(target, 'group-call-button') || 
            findUpClassName(target, 'btn-icon') ||
            findUpClassName(target, 'group-call-participants-video-container') ||
            isFullScreen()) {
            return false;
          }

          return true;
        }
      },
      onResize: () => this.toggleBigLayout(),
      previousState
    });

    listenerSetter.add(instance)('state', () => {
      this.updateInstance();
    });

    listenerSetter.add(rootScope)('group_call_update', (groupCall) => {
      if(this.instance?.id === groupCall.id) {
        this.updateInstance();
      }
    });

    listenerSetter.add(instance)('pinned', () => {
      this.setHasPinned();
    });

    listenerSetter.add(this.groupCallParticipantsVideo)('toggleControls', this.onToggleControls);

    this.addEventListener('close', () => {
      const {movablePanel} = this;
      previousState = movablePanel.state;

      this.groupCallParticipantsVideo.destroy();
      this.groupCallParticipants.destroy();
      this.groupCallMicrophoneIcon.destroy();

      movablePanel.destroy();
    });

    this.toggleRightColumn();
    this.onFullScreenChange();

    this.updateInstance();
  }

  private constructButtons() {
    const buttons = this.buttonsContainer = document.createElement('div');
    buttons.classList.add(className + '-buttons');

    const _makeButton = makeButton.bind(null, className, this.listenerSetter);

    const btnVideo = this.btnVideo = _makeButton({
      // text: 'VoiceChat.Video.Stream.Video',
      callback: this.onVideoClick,
      icon: 'videocamera_filled'
    });

    const btnScreen = this.btnScreen = _makeButton({
      // text: 'VoiceChat.Video.Stream.Screencast',
      callback: this.onScreenClick,
      icon: 'sharescreen_filled'
    });

    btnScreen.classList.toggle('hide', !IS_SCREEN_SHARING_SUPPORTED);

    const btnMute = _makeButton({
      noRipple: true,
      callback: throttle(this.onMuteClick, 600, true)
    });
    btnMute.classList.add(className + '-microphone-button');

    const microphoneIcon = this.groupCallMicrophoneIcon = new GroupCallMicrophoneIcon();
    btnMute.append(microphoneIcon.container);

    const btnMore = _makeButton({
      // text: 'VoiceChat.Video.Stream.More'
      icon: 'settings_filled'
    });

    btnMore.classList.add('btn-disabled');
    btnMore.classList.toggle('hide', !IS_SCREEN_SHARING_SUPPORTED);

    const btnLeave = _makeButton({
      // text: 'VoiceChat.Leave',
      isDanger: true,
      callback: this.onLeaveClick,
      icon: 'close'
    });

    buttons.append(btnVideo, btnScreen, btnMute, btnMore, btnLeave);

    this.container.append(buttons);
  }

  private onFullScreenClick = () => {
    requestFullScreen(this.container);
  };

  private onToggleControls = (show: boolean) => {
    this.container.classList.toggle('show-controls', show);
    this.buttonsContainer.classList.toggle('show-controls', show);
  };

  private toggleDisability = toggleClassName.bind(null, 'btn-disabled');

  private onVideoClick = () => {
    const toggle = this.toggleDisability([this.btnVideo], true);
    this.instance.toggleVideoSharing().finally(() => {
      toggle();
    });
  };

  private onScreenClick = () => {
    const toggle = this.toggleDisability([this.btnScreen], true);
    this.instance.toggleScreenSharing().finally(() => {
      toggle();
    });
  };

  private onMuteClick = () => {
    const participant = this.instance.participant;
    if(!participant.pFlags.can_self_unmute) {
      if(participant.raise_hand_rating === undefined) {
        this.instance.changeRaiseHand(true);
      }
    } else {
      this.instance.toggleMuted();
    }
  };
  
  private onLeaveClick = async() => {
    const hangUp = (discard: boolean) => {
      this.instance.hangUp(discard);
    };

    if(await this.managers.appChatsManager.hasRights(this.instance.chatId, 'manage_call')) {
      new PopupPeer('popup-end-video-chat', {
        titleLangKey: 'VoiceChat.End.Title',
        descriptionLangKey: 'VoiceChat.End.Text',
        checkboxes: [{
          text: 'VoiceChat.End.Third'
        }],
        buttons: [{
          langKey: 'VoiceChat.End.OK',
          callback: (checkboxes) => {
            hangUp(!!checkboxes.size);
          },
          isDanger: true,
        }]
      }).show();
    } else {
      hangUp(false);
    }
  };

  public getContainer() {
    return this.container;
  }

  private onFullScreenChange = () => {
    this.toggleBigLayout();
    const isFull = isFullScreen();

    const {btnFullScreen, btnExitFullScreen} = this;

    const wasFullScreen = this.container.classList.contains('is-full-screen');
    this.container.classList.toggle('is-full-screen', isFull);
    btnFullScreen && btnFullScreen.classList.toggle('hide', isFull);
    btnExitFullScreen && btnExitFullScreen.classList.toggle('hide', !isFull);
    this.btnClose.classList.toggle('hide', isFull);

    if(isFull !== wasFullScreen) {
      animationIntersector.checkAnimations(isFull);

      themeController.setThemeColor(isFull ? '#000000' : undefined);
    }
  };

  private toggleBigLayout = () => {
    const isFull = isFullScreen();
    const movable = this.movablePanel?.movable;
    const isBig = (isFull || !!(movable && movable.width >= 680)) && !!this.videosCount;

    /* if(!isBig && isFull) {
      cancelFullScreen();
      return;
    } */

    const wasBig = this.container.classList.contains('is-big-layout');
    let buttons: HTMLElement[];
    if(isBig && !wasBig) { // fix buttons transition to 0 opacity
      buttons = Array.from(this.buttonsContainer.children) as HTMLElement[];
      buttons.forEach((element) => {
        element.style.opacity = '0';
      });

      void this.buttonsContainer.offsetLeft;
    }

    this.container.classList.toggle('is-big-layout', isBig);
    this.btnInvite.classList.toggle('hide', isBig);
    this.btnShowColumn.classList.toggle('hide', !isBig);

    if(buttons) {
      // window.requestAnimationFrame(() => {
        buttons.forEach((element) => {
          element.style.opacity = '';
        });
      // });
    }
  };

  private toggleRightColumn = () => {
    this.container.classList.toggle('is-right-column-shown');
  };

  private setHasPinned() {
    this.container.classList.toggle('has-pinned', !!this.instance.pinnedSource);
  }

  private updateInstance() {
    if(this.instance.state === GROUP_CALL_STATE.CLOSED) {
      if(this.container.classList.contains('is-full-screen')) {
        cancelFullScreen();
      }

      this.hide();
      return;
    }

    const {participant, groupCall} = this.instance;
    if(!participant) {
      return;
    }

    this.setTitle();
    this.setDescription();
    this.setHasPinned();

    const microphoneButtonState = getGroupCallMicrophoneButtonState(groupCall as any, participant);
    this.container.dataset.micState = microphoneButtonState === GROUP_CALL_MICROPHONE_BUTTON_STATE.HAND ? 'hand' : (microphoneButtonState === GROUP_CALL_MICROPHONE_BUTTON_STATE.MUTED ? 'muted' : 'unmuted');
    this.groupCallMicrophoneIcon.setState(microphoneButtonState);
  }

  private setTitle() {
    this.groupCallTitle.update(this.instance);
  }

  private setDescription() {
    this.groupCallDescription.update(this.instance);
    this.groupCallBodyHeaderDescription.update(this.instance);
  }
}
