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

import IS_TOUCH_SUPPORTED from "../../environment/touchSupport";
import { IS_MOBILE, IS_SAFARI } from "../../environment/userAgent";
import assumeType from "../../helpers/assumeType";
import callbackify from "../../helpers/callbackify";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
import findUpClassName from "../../helpers/dom/findUpClassName";
import getVisibleRect from "../../helpers/dom/getVisibleRect";
import { getMiddleware } from "../../helpers/middleware";
import noop from "../../helpers/noop";
import { fastRaf } from "../../helpers/schedulers";
import { Message, AvailableReaction } from "../../layer";
import { AppManagers } from "../../lib/appManagers/managers";
import lottieLoader from "../../lib/rlottie/lottieLoader";
import RLottiePlayer from "../../lib/rlottie/rlottiePlayer";
import rootScope from "../../lib/rootScope";
import animationIntersector from "../animationIntersector";
import Scrollable, { ScrollableBase, ScrollableX } from "../scrollable";
import { wrapSticker } from "../wrappers";

const REACTIONS_CLASS_NAME = 'btn-menu-reactions';
const REACTION_CLASS_NAME = REACTIONS_CLASS_NAME + '-reaction';

const REACTION_SIZE = 28;
const PADDING = 4;
const REACTION_CONTAINER_SIZE = REACTION_SIZE + PADDING * 2;

const CAN_USE_TRANSFORM = !IS_SAFARI;

type ChatReactionsMenuPlayers = {
  select?: RLottiePlayer,
  appear?: RLottiePlayer,
  selectWrapper: HTMLElement,
  appearWrapper: HTMLElement,
  reaction: string
};
export class ChatReactionsMenu {
  public widthContainer: HTMLElement;
  private container: HTMLElement;
  private reactionsMap: Map<HTMLElement, ChatReactionsMenuPlayers>;
  private scrollable: ScrollableBase;
  private animationGroup: string;
  private middleware: ReturnType<typeof getMiddleware>;
  private message: Message.message;

  constructor(
    private managers: AppManagers,
    private type: 'horizontal' | 'vertical',
    middleware: ChatReactionsMenu['middleware']
  ) {
    const widthContainer = this.widthContainer = document.createElement('div');
    widthContainer.classList.add(REACTIONS_CLASS_NAME + '-container');
    widthContainer.classList.add(REACTIONS_CLASS_NAME + '-container-' + type);

    const reactionsContainer = this.container = document.createElement('div');
    reactionsContainer.classList.add(REACTIONS_CLASS_NAME);

    const reactionsScrollable = this.scrollable = type === 'vertical' ? new Scrollable(undefined) : new ScrollableX(undefined);
    reactionsContainer.append(reactionsScrollable.container);
    reactionsScrollable.onAdditionalScroll = this.onScroll;
    reactionsScrollable.setListeners();

    reactionsScrollable.container.classList.add('no-scrollbar');

    ['big'].forEach((type) => {
      const bubble = document.createElement('div');
      bubble.classList.add(REACTIONS_CLASS_NAME + '-bubble', REACTIONS_CLASS_NAME + '-bubble-' + type);
      reactionsContainer.append(bubble);
    });

    this.reactionsMap = new Map();
    this.animationGroup = 'CHAT-MENU-REACTIONS-' + Date.now();
    animationIntersector.setOverrideIdleGroup(this.animationGroup, true);

    if(!IS_TOUCH_SUPPORTED) {
      reactionsContainer.addEventListener('mousemove', this.onMouseMove);
    }

    attachClickEvent(reactionsContainer, (e) => {
      const reactionDiv = findUpClassName(e.target, REACTION_CLASS_NAME);
      if(!reactionDiv) return;

      const players = this.reactionsMap.get(reactionDiv);
      if(!players) return;

      this.managers.appReactionsManager.sendReaction(this.message, players.reaction);
    });

    widthContainer.append(reactionsContainer);

    this.middleware = middleware ?? getMiddleware();
  }

  public init(message: Message.message) {
    this.message = message;

    const middleware = this.middleware.get();
    // const result = Promise.resolve(this.appReactionsManager.getAvailableReactionsForPeer(message.peerId)).then((res) => pause(1000).then(() => res));
    const result = this.managers.appReactionsManager.getAvailableReactionsByMessage(message);
    callbackify(result, (reactions) => {
      if(!middleware() || !reactions.length) return;
      reactions.forEach((reaction) => {
        this.renderReaction(reaction);
      });

      const setVisible = () => {
        this.container.classList.add('is-visible');
      };

      if(result instanceof Promise) {
        fastRaf(setVisible);
      } else {
        setVisible();
      }
    });
  }

  public cleanup() {
    this.middleware.clean();
    this.scrollable.removeListeners();
    this.reactionsMap.clear();
    animationIntersector.setOverrideIdleGroup(this.animationGroup, false);
    animationIntersector.checkAnimations(true, this.animationGroup, true);
  }

  private onScroll = () => {
    this.reactionsMap.forEach((players, div) => {
      this.onScrollProcessItem(div, players);
    });
  };

  private canUseAnimations() {
    return rootScope.settings.animationsEnabled && !IS_MOBILE;
  }

  private renderReaction(reaction: AvailableReaction) {
    const reactionDiv = document.createElement('div');
    reactionDiv.classList.add(REACTION_CLASS_NAME);

    const scaleContainer = document.createElement('div');
    scaleContainer.classList.add(REACTION_CLASS_NAME + '-scale');

    const appearWrapper = document.createElement('div');
    let selectWrapper: HTMLElement;;
    appearWrapper.classList.add(REACTION_CLASS_NAME + '-appear');

    if(this.canUseAnimations()) {
      selectWrapper = document.createElement('div');
      selectWrapper.classList.add(REACTION_CLASS_NAME + '-select', 'hide');
    }

    const players: ChatReactionsMenuPlayers = {
      selectWrapper,
      appearWrapper,
      reaction: reaction.reaction
    };
    this.reactionsMap.set(reactionDiv, players);

    const middleware = this.middleware.get();

    const hoverScale = IS_TOUCH_SUPPORTED ? 1 : 1.25;
    const size = REACTION_SIZE * hoverScale;

    const options = {
      width: size,
      height: size,
      skipRatio: 1,
      needFadeIn: false,
      withThumb: false,
      group: this.animationGroup,
      middleware
    };

    if(!this.canUseAnimations()) {
      delete options.needFadeIn;
      delete options.withThumb;

      wrapSticker({
        doc: reaction.static_icon,
        div: appearWrapper,
        ...options
      });
    } else {
      let isFirst = true;
      wrapSticker({
        doc: reaction.appear_animation,
        div: appearWrapper,
        play: true,
        ...options
      }).then(({render}) => render).then((player) => {
        assumeType<RLottiePlayer>(player);
  
        players.appear = player;
  
        player.addEventListener('enterFrame', (frameNo) => {
          if(player.maxFrame === frameNo) {
            selectLoadPromise.then((selectPlayer) => {
              assumeType<RLottiePlayer>(selectPlayer);
              appearWrapper.classList.add('hide');
              selectWrapper.classList.remove('hide');
  
              if(isFirst) {
                players.select = selectPlayer;
                isFirst = false;
              }
            }, noop);
          }
        });
      }, noop);
  
      const selectLoadPromise = wrapSticker({
        doc: reaction.select_animation,
        div: selectWrapper,
        ...options
      }).then(({render}) => render).then((player) => {
        assumeType<RLottiePlayer>(player);

        return lottieLoader.waitForFirstFrame(player);
      }).catch(noop);
    }
    
    scaleContainer.append(appearWrapper);
    selectWrapper && scaleContainer.append(selectWrapper);
    reactionDiv.append(scaleContainer);
    this.scrollable.append(reactionDiv);
  }

  private onScrollProcessItem(div: HTMLElement, players: ChatReactionsMenuPlayers) {
    // return;

    const scaleContainer = div.firstElementChild as HTMLElement;
    const visibleRect = getVisibleRect(div, this.scrollable.container);
    let transform: string;
    if(!visibleRect) {
      if(!players.appearWrapper.classList.contains('hide') || !players.appear) {
        return;
      }

      if(players.select) {
        players.select.stop();
      }

      players.appear.stop();
      players.appear.autoplay = true;
      players.appearWrapper.classList.remove('hide');
      players.selectWrapper.classList.add('hide');

      transform = '';
    } else if(visibleRect.overflow.left || visibleRect.overflow.right) {
      const diff = Math.abs(visibleRect.rect.left - visibleRect.rect.right);
      const scale = Math.min(diff ** 2 / REACTION_CONTAINER_SIZE ** 2, 1);

      transform = 'scale(' + scale + ')';
    } else {
      transform = '';
    }

    if(CAN_USE_TRANSFORM) {
      scaleContainer.style.transform = transform;
    }
  }

  private onMouseMove = (e: MouseEvent) => {
    const reactionDiv = findUpClassName(e.target, REACTION_CLASS_NAME);
    if(!reactionDiv) {
      return;
    }
    
    const players = this.reactionsMap.get(reactionDiv);
    if(!players) {
      return;
    }

    // do not play select animation when appearing
    if(!players.appear?.paused) {
      return;
    }

    const player = players.select;
    if(!player) {
      return;
    }

    if(player.paused) {
      player.autoplay = true;
      player.restart();
    }
  };
}
