import {
  Component,
  OnInit,
  Input,
  EventEmitter,
  Output,
  ViewChild,
  OnDestroy,
  ChangeDetectionStrategy,
  ElementRef,
  ChangeDetectorRef,
  ViewChildren,
  QueryList
} from '@angular/core';

import {
  Info,
  EventType,
  InfoResponse,
  EventJson,
  FileEventJson,
  MassTranslationEventJson
} from '@ucap-webmessenger/protocol-event';
import { LoginResponse } from '@ucap-webmessenger/protocol-authentication';
import { UserInfo, RoomInfo, RoomType } from '@ucap-webmessenger/protocol-room';
import { NGXLogger } from 'ngx-logger';
import { VersionInfo2Response } from '@ucap-webmessenger/api-public';
import moment from 'moment';
import { FileDownloadItem } from '@ucap-webmessenger/api';
import { TranslateService } from '@ngx-translate/core';
import { Observable, Subscription, timer } from 'rxjs';
import { VirtualScrollerComponent, IPageInfo } from 'ngx-virtual-scroller';
import { MessageBoxComponent } from './message-box.component';
import { debounce } from 'rxjs/operators';
import { SelectFileInfo } from '@ucap-webmessenger/ui';

@Component({
  selector: 'ucap-chat-messages',
  templateUrl: './messages.component.html',
  styleUrls: ['./messages.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MessagesComponent implements OnInit, OnDestroy {
  @Input()
  loginRes$: Observable<LoginResponse>;
  @Input()
  roomInfo$: Observable<RoomInfo>;
  @Input()
  eventList$: Observable<Info<EventJson>[]>;
  @Input()
  newEventList$: Observable<Info<EventJson>[]>;
  @Input()
  searchedList$: Observable<Info<EventJson>[]>;
  @Input()
  eventInfoStatus$: Observable<InfoResponse>;
  @Input()
  eventRemained$: Observable<boolean>;
  @Input()
  sessionVerInfo: VersionInfo2Response;
  @Input()
  userInfos$: Observable<UserInfo[]>;

  @Input()
  lock$: Observable<boolean>;

  @Input()
  isShowUnreadCount = true;
  @Input()
  clearReadHere: boolean;
  @Input()
  minShowReadHere = 10;
  @Input()
  translationSimpleview = false;
  @Input()
  searchingMode = false;

  @Output()
  openProfile = new EventEmitter<number>();
  @Output()
  moreEvent = new EventEmitter<number>();
  @Output()
  massDetail = new EventEmitter<number>();
  @Output()
  massTranslationDetail = new EventEmitter<{
    message: Info<MassTranslationEventJson>;
    contentsType: string;
  }>();
  @Output()
  fileViewer = new EventEmitter<SelectFileInfo>();
  @Output()
  save = new EventEmitter<{
    fileInfo: FileEventJson;
    fileDownloadItem: FileDownloadItem;
    type: string;
  }>();
  @Output()
  contextMenu = new EventEmitter<{
    event: MouseEvent;
    message: Info<EventJson>;
    type?: string;
  }>();

  @Output()
  scrollUp = new EventEmitter<any>();

  @Output()
  yReachEnd = new EventEmitter<any>();
  @Output()
  yReachStart = new EventEmitter<any>();

  @Output()
  existNewMessage = new EventEmitter<Info<EventJson>>();

  @ViewChild('chatMessagesContainer', { static: false })
  chatMessagesContainer: ElementRef<HTMLElement>;

  @ViewChild('chatMessagesBuffer', { static: false })
  chatMessagesBuffer: ElementRef<HTMLElement>;

  @ViewChild('chatMessagesBufferContainer', { static: false })
  chatMessagesBufferContainer: ElementRef<HTMLElement>;

  @ViewChild(VirtualScrollerComponent, { static: false })
  private virtualScroller: VirtualScrollerComponent;

  @ViewChildren('chatMessageBox')
  chatMessageBoxList: QueryList<MessageBoxComponent>;

  storedScrollItem: Info<EventJson>; // 이전대화를 불러올 경우 현재 스크롤 포지션 유지를 위한 값. 0 이면 초기로딩.
  storedScrollItemOffsetTop: number | undefined;
  scrollUpInitalized = false; // ps 에서 초기 로딩시 scroll reach start 이벤트 발생 버그를 우회하기 위한 init 값으로 scrollUp 에 의해 true 로 된다.
  firstCheckReadHere = true;
  initRoomLastEventSeq: number;
  baseEventSeq = 0;
  gotoEventSeq: number;

  loginRes: LoginResponse;
  loginResSubscription: Subscription;
  roomInfo: RoomInfo;
  roomInfoSubscription: Subscription;
  eventList: Info<EventJson>[];
  eventListSubscription: Subscription;
  newEventList: Info<EventJson>[];
  newEventListSubscription: Subscription;
  searchedList: Info<EventJson>[];
  searchedListSubscription: Subscription;
  eventInfoStatus: InfoResponse;
  eventInfoStatusSubscription: Subscription;
  eventRemained: boolean;
  eventRemainedSubscription: Subscription;
  userInfos: UserInfo[];
  userInfosSubscription: Subscription;

  EventType = EventType;
  profileImageRoot: string;
  moment = moment;

  readToHereEvent: Info<EventJson>;
  existReadToHereEvent = true;
  hidden = false;
  swapped = false;
  initalized = false;

  constructor(
    private logger: NGXLogger,
    private changeDetectorRef: ChangeDetectorRef,
    private translateService: TranslateService
  ) {}

  ngOnInit() {
    this.profileImageRoot =
      this.profileImageRoot || this.sessionVerInfo.profileRoot;

    this.loginResSubscription = this.loginRes$.subscribe(loginRes => {
      this.loginRes = loginRes;
    });
    this.roomInfoSubscription = this.roomInfo$.subscribe(roomInfo => {
      this.roomInfo = roomInfo;
      this.initalized = false;

      /** [S] initializing by changed room */
      // reset :: roomLastEventSeq
      if (!!roomInfo && !!roomInfo.finalEventSeq) {
        this.initRoomLastEventSeq = roomInfo.finalEventSeq;
      }
      // clear :: readToHearEvent object
      this.readToHereEvent = undefined;
      this.existReadToHereEvent = true;
      /** [E] initializing by changed room */

      if (!this.roomInfo || this.roomInfo.roomSeq !== roomInfo.roomSeq) {
        this.initEventMore();
      }
    });
    this.eventListSubscription = this.eventList$
      .pipe(debounce(() => timer(100)))
      .subscribe(eventList => {
        this.eventList = eventList;

        if (!!eventList && eventList.length > 0) {
          if (!this.readToHereEvent && this.existReadToHereEvent) {
            this.readToHereEvent = this.getReadHere();
          }

          if (
            this.baseEventSeq > 0 &&
            !!this.roomInfo &&
            !!this.roomInfo.lastReadEventSeq &&
            this.baseEventSeq <= this.roomInfo.lastReadEventSeq
          ) {
            // 기존 대화 내용이 있는 상태에서 추가로 조회된 내용중에 read here 가 있을 경우.
            this.firstCheckReadHere = false;
          }
        } else {
          this.readToHereEvent = undefined;
        }

        this.changeDetectorRef.detectChanges();

        if (this.searchingMode) {
          const baseseq = this.baseEventSeq;
          // setTimeout(() => {
          //   this.doSearchTextInEvent(this.searchText, baseseq);
          // }, 800);
          this.baseEventSeq = eventList[0].seq;
        } else {
          if (!!eventList && eventList.length > 0) {
            this.baseEventSeq = eventList[0].seq;
            this.ready();
          }
        }
      });
    this.newEventListSubscription = this.newEventList$.subscribe(
      newEventList => {
        this.newEventList = newEventList;
      }
    );
    this.searchedListSubscription = this.searchedList$.subscribe(
      searchedList => {
        this.searchedList = searchedList;
      }
    );
    this.eventInfoStatusSubscription = this.eventInfoStatus$.subscribe(
      eventInfoStatus => {
        this.eventInfoStatus = eventInfoStatus;
      }
    );
    this.eventRemainedSubscription = this.eventRemained$.subscribe(
      eventRemained => {
        this.eventRemained = eventRemained;
      }
    );
    this.userInfosSubscription = this.userInfos$.subscribe(userInfos => {
      this.userInfos = userInfos;
    });
  }

  ngOnDestroy(): void {
    if (!!this.loginResSubscription) {
      this.loginResSubscription.unsubscribe();
    }
    if (!!this.roomInfoSubscription) {
      this.roomInfoSubscription.unsubscribe();
    }
    if (!!this.eventListSubscription) {
      this.eventListSubscription.unsubscribe();
    }
    if (!!this.newEventListSubscription) {
      this.newEventListSubscription.unsubscribe();
    }
    if (!!this.searchedListSubscription) {
      this.searchedListSubscription.unsubscribe();
    }
    if (!!this.eventInfoStatusSubscription) {
      this.eventInfoStatusSubscription.unsubscribe();
    }
    if (!!this.eventRemainedSubscription) {
      this.eventRemainedSubscription.unsubscribe();
    }
    if (!!this.userInfosSubscription) {
      this.userInfosSubscription.unsubscribe();
    }
  }

  /**
   * UserInfo getter
   */
  getUserName(seq: number): string {
    if (!this.userInfos) {
      return '';
    }

    const userInfo: UserInfo[] = this.userInfos.filter(
      user => user.seq === seq
    );
    if (!!userInfo && userInfo.length > 0) {
      return userInfo[0].name;
    }
    return '(알수없는 사용자)';
  }
  getUserProfile(seq: number): string {
    if (!this.userInfos) {
      return '';
    }

    const userInfo: UserInfo[] = this.userInfos.filter(
      user => user.seq === seq
    );
    if (!!userInfo && userInfo.length > 0) {
      return userInfo[0].profileImageFile;
    }
    return '';
  }

  isHighlightedEvent(seq: number): boolean {
    return (
      !!this.searchedList &&
      this.searchedList.filter(event => event.seq === seq).length > 0
    );
  }

  getUnreadCount(message: Info<EventJson>): string | number {
    // if (!this.userInfos || 0 === this.userInfos.length) {
    //   return '';
    // }
    const unreadCnt = this.userInfos
      .filter(user => user.isJoinRoom && user.seq !== message.senderSeq)
      .filter(user => user.lastReadEventSeq < message.seq).length;
    return unreadCnt === 0 ? '' : unreadCnt;
  }

  /**
   * 정보성 Event 인지 판단.
   * @description 정보성 event 일 경우 프로필, 일시 를 표현하지 않는다.
   * Edit with reducers.ts / sync / updateRoomForNewEventMessage
   */
  getIsInformation(info: Info<EventJson>) {
    if (
      info.type === EventType.Join ||
      info.type === EventType.Exit ||
      info.type === EventType.ForcedExit ||
      info.type === EventType.RenameRoom ||
      info.type === EventType.NotificationForTimerRoom ||
      info.type === EventType.GuideForRoomTimerChanged
    ) {
      return true;
    }
    return false;
  }

  /** Date Splitter show check */
  getDateSplitter(message: Info<EventJson>): boolean {
    const curIndex = this.eventList.findIndex(v => v.seq === message.seq);

    if (curIndex === 0) {
      return true;
    }
    if (curIndex > 0) {
      if (!this.eventList[curIndex]) {
        return false;
      }
      return !moment(this.eventList[curIndex].sendDate).isSame(
        moment(this.eventList[curIndex - 1].sendDate),
        'day'
      );
    }
    return false;
  }

  getReadHere(): Info<EventJson> | undefined {
    if (
      !!this.roomInfo &&
      !!this.roomInfo.lastReadEventSeq &&
      this.initRoomLastEventSeq - this.roomInfo.lastReadEventSeq >
        this.minShowReadHere
    ) {
      if (
        this.roomInfo.roomType === RoomType.Single ||
        this.roomInfo.roomType === RoomType.Multi
      ) {
        if (!this.roomInfo.isTimeRoom) {
          return this.eventList.find(
            v => v.seq === this.roomInfo.lastReadEventSeq + 1
          );
        }
      }
    } else {
      this.existReadToHereEvent = false;
    }
    return undefined;
  }

  getStringReadHereMore(): string {
    let rtnStr = '';
    rtnStr = this.translateService.instant('chat.event.moreUnreadEventsWith', {
      countOfUnread: this.baseEventSeq - (this.roomInfo.lastReadEventSeq + 1)
    });
    return rtnStr;
  }

  storeScrollPosition() {
    this.storedScrollItem = this.eventList[
      this.virtualScroller.viewPortInfo.startIndex
    ];

    const chatMessageBox = this.chatMessageBoxList.find(
      el =>
        el.message.seq ===
        this.eventList[this.virtualScroller.viewPortInfo.startIndex].seq
    );
    if (!!chatMessageBox) {
      this.storedScrollItemOffsetTop =
        chatMessageBox.offsetTop -
        this.virtualScroller.viewPortInfo.scrollStartPosition;
    } else {
      this.storedScrollItemOffsetTop = 0;
    }
  }

  swapScrollTo(
    to: Info<EventJson>,
    preCallback: () => void,
    postCallback: () => void,
    useHide: boolean,
    useSwap: boolean,
    addtionalOffset?: number
  ) {
    this.preSwapScroll(useHide, useSwap);
    if (!!preCallback) {
      preCallback();
    }

    this.virtualScroller.scrollInto(
      to,
      true,
      undefined !== this.storedScrollItemOffsetTop
        ? -this.storedScrollItemOffsetTop
        : undefined !== addtionalOffset
        ? -addtionalOffset
        : 0,
      0,
      () => {
        setTimeout(() => {
          if (!!postCallback) {
            postCallback();
          }
          this.postSwapScroll(useHide, useSwap);
        });
      }
    );
  }

  preSwapScroll(useHide: boolean, useSwap: boolean) {
    if (useSwap && !this.swapped) {
      this.chatMessagesBuffer.nativeElement.innerHTML = this.chatMessagesContainer.nativeElement.innerHTML;
      this.chatMessagesBuffer.nativeElement.scrollTop = this.chatMessagesContainer.nativeElement.scrollTop;
      this.chatMessagesBufferContainer.nativeElement.classList.remove(
        'disappear'
      );
      this.swapped = true;
    }

    if (useHide && !this.hidden) {
      this.chatMessagesContainer.nativeElement.classList.add('hide');
      this.hidden = true;
    }
  }

  postSwapScroll(useHide: boolean, useSwap: boolean) {
    if (useSwap && this.swapped) {
      this.chatMessagesBuffer.nativeElement.innerHTML = '';
      this.chatMessagesBuffer.nativeElement.scrollTop = 0;
      this.chatMessagesBufferContainer.nativeElement.classList.add('disappear');
      this.swapped = false;
    }

    if (useHide && this.hidden) {
      this.chatMessagesContainer.nativeElement.classList.remove('hide');
      this.hidden = false;
    }
  }

  ready(): void {
    this.scrollToBottom();
  }

  scrollToBottom(): void {
    if (!!this.storedScrollItem) {
      if (!!this.readToHereEvent && this.firstCheckReadHere) {
        this.swapScrollTo(
          this.readToHereEvent,
          () => {},
          () => {
            this.firstCheckReadHere = false;
          },
          true,
          true
        );
      } else {
        this.swapScrollTo(
          this.storedScrollItem,
          () => {},
          () => {
            this.storedScrollItem = undefined;
            this.storedScrollItemOffsetTop = undefined;
          },
          true,
          true
        );
      }
    } else if (this.scrollUpInitalized) {
      if (!!this.newEventList && this.newEventList.length > 0) {
        this.existNewMessage.emit(
          this.newEventList[this.newEventList.length - 1]
        );
      }
    } else {
      if (!!this.readToHereEvent && this.firstCheckReadHere) {
        this.swapScrollTo(
          this.readToHereEvent,
          () => {},
          () => {
            this.firstCheckReadHere = false;
          },
          false,
          false
        );
      } else {
        if (!this.eventList || 0 === this.eventList.length) {
          return;
        }

        if (!!this.gotoEventSeq) {
          this.gotoPosition(this.gotoEventSeq);
          this.gotoEventSeq = undefined;
          return;
        }

        const lastEvent =
          !!this.eventList && 0 < this.eventList.length
            ? this.eventList[this.eventList.length - 1]
            : undefined;

        if (undefined === lastEvent) {
          return;
        }
        const isInViewPortItems = this.isInViewPortItems(lastEvent.seq);
        this.swapScrollTo(
          this.eventList[this.eventList.length - 1],
          () => {},
          () => {
            this.initalized = true;
          },
          -1 === this.virtualScroller.viewPortInfo.endIndex ||
            !isInViewPortItems,
          !isInViewPortItems
        );
      }
    }
  }

  initEventMore(gotoEventSeq?: number) {
    // 방정보가 바뀌면 이전대화 보기 관련 값들을 초기화 한다.
    this.scrollUpInitalized = false;
    this.storedScrollItem = undefined;
    this.storedScrollItemOffsetTop = undefined;
    this.gotoEventSeq = gotoEventSeq;
  }

  clear() {}

  gotoPosition(eventSeq: number) {
    const isInViewPortItems = this.isInViewPortItems(eventSeq);

    if (!!this.virtualScroller) {
      const e = this.eventList.find(v => v.seq === eventSeq);
      this.swapScrollTo(
        e,
        () => {},
        () => {
          const chatMessageBox = this.chatMessageBoxList.find(
            el => el.message.seq === eventSeq
          );
          if (!!chatMessageBox) {
            chatMessageBox.shake();
          }
        },
        !isInViewPortItems,
        !isInViewPortItems,
        50
      );
    }
  }

  isInViewPortItems(eventSeq: number): boolean {
    if (undefined === eventSeq) {
      return false;
    }
    const viewPortItemIndex = this.virtualScroller.viewPortItems.findIndex(
      v => v.seq === eventSeq
    );

    const newEvent =
      !!this.newEventList &&
      -1 !== this.newEventList.findIndex(e => e.seq === eventSeq);

    return -1 !== viewPortItemIndex || newEvent;
  }

  onClickOpenProfile(userSeq: number) {
    this.openProfile.emit(userSeq);
  }

  onClickMore(event: any) {
    event.preventDefault();
    event.stopPropagation();

    if (this.scrollUpInitalized && this.eventRemained) {
      this.virtualScroller.scrollToPosition(0);
      this.virtualScroller.invalidateCachedMeasurementAtIndex(0);

      this.storeScrollPosition();

      this.preSwapScroll(true, true);

      this.moreEvent.emit(this.eventList[0].seq);
    }
  }

  /** [Event] MassTalk Detail View */
  onMassDetail(value: number) {
    this.massDetail.emit(value);
  }

  onMassTranslationDetail(params: {
    message: Info<MassTranslationEventJson>;
    contentsType: string;
  }) {
    this.massTranslationDetail.emit(params);
  }

  /** [Event] Image Viewer */
  onFileViewer(fileInfo: SelectFileInfo) {
    this.fileViewer.emit(fileInfo);
  }

  /** [Event] Attach File Save & Save As */
  onSave(value: {
    fileInfo: FileEventJson;
    fileDownloadItem: FileDownloadItem;
    type: string;
  }) {
    this.save.emit(value);
  }

  /** [Event] Context Menu */
  onContextMenu(event: {
    event: MouseEvent;
    message: Info<EventJson>;
    type?: string;
  }) {
    this.contextMenu.emit(event);
  }

  onScrollup(event: any) {
    if (!this.eventList || 0 === this.eventList.length) {
      return;
    }
    this.scrollUpInitalized = true;
    this.scrollUp.emit(event);
  }
  onYReachStart(event: any) {
    this.yReachStart.emit(event);
  }
  onYReachEnd(event: any) {
    this.yReachEnd.emit(event);
  }

  onVsChange(event: IPageInfo) {
    if (
      -1 === event.startIndex ||
      -1 === event.endIndex ||
      (0 === event.startIndex && 0 === event.endIndex)
    ) {
      return;
    }
    // this.logger.debug('onVsChange', event);
  }

  trackByEvent(index: number, info: Info<EventJson>): number {
    return info.seq;
  }

  compareItemsFunc = (
    item1: Info<EventJson>,
    item2: Info<EventJson>
    // tslint:disable-next-line: semicolon
  ): boolean => !!item1 && !!item2 && item1.seq === item2.seq;
}