import { Whiteboard, WhiteboardApplication, WhiteboardPermissionFlag } from '@netless/forge-whiteboard';
import { ISlideConfig, Slide, SLIDE_EVENTS } from '@netless/slide';
import { AbstractApplication, kvStore } from '@netless/forge-room';
import { SlideForge } from './Slide';
import { ForgeSlidePermissionFlag, ForgeSlidePermissions } from './ForgeSlidePermession';
import { FooterView } from './FooterView';
import * as Y from 'yjs';
import { SideBarView } from './SiderBarView';
import { deepEqual } from './utils';

export interface SlideApplicationOption {
    prefix: string;
    taskId: string;
    options?: ISlideConfig;
    /**
     * 从指定的白板 id 继承白板权限以及工具栏配置
     */
    inheritWhiteboardId?: string;
}

interface PreviewImage {
    url: string;
    src: string;
    width: number;
    height: number;
}

export const Slide_APP_NAME = 'forge_slide';

export class SlideApplication extends AbstractApplication<SlideApplicationOption, SlideForge> {

  static applicationName = Slide_APP_NAME;

  public readonly name: string = Slide_APP_NAME;
  public readonly emitter: SlideForge = new SlideForge();

  private whiteboardApp!: WhiteboardApplication;
  private whiteboard!: Whiteboard;
  private rootView: HTMLDivElement = document.createElement('div');
  private contentContainer: HTMLDivElement = document.createElement('div');
  private whiteboardContainer: HTMLDivElement = document.createElement('div');
  private slideContainer: HTMLDivElement = document.createElement('div');
  private permissions!: ForgeSlidePermissions;
  private footer: FooterView;
  private sideBar: SideBarView;
  private slide!: Slide;
  private currentSlideIndex: number = 0;
  private taskId: string = '';
  private prefix: string = '';
  private slideCount: number = 0;
  private lastDispatchUuid: string = '';
  private syncMessageQueue: { state: any, dispatch: any }[] = [];
  private isSyncing: boolean = false;

  constructor() {
    super();
    (window as any).emitter = this.emitter;
    this.rootView.setAttribute('data-forge-app', Slide_APP_NAME);
    this.rootView.style.background = '#f9f9fc';
    this.rootView.style.overflow = 'hidden';

    this.contentContainer.style.width = '100%';
    this.contentContainer.style.height = '100%';
    this.contentContainer.style.display = 'flex';
    this.contentContainer.style.flexDirection = 'column';

    this.slideContainer.style.width = '100%';
    this.slideContainer.style.height = '100%';

    this.footer = new FooterView();
    this.sideBar = new SideBarView();
    this.sideBar.on('pageChange', index => {
      if (index > 0 && index <= this.slideCount) {
        this.slide.renderSlide(index);
      }
    });
    this.footer.on('prevStep', () => {
      if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changeStep)) {
        return;
      }
      if (this.slide.mainSeqStep > 0 || this.slide.mainSeqState !== 'idle') {
        this.slide.prevStep();
      } else if (this.currentSlideIndex > 1){
        if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
          return;
        }
        this.slide.renderSlide(this.currentSlideIndex - 1);
      }
    });
    this.footer.on('nextStep', () => {
      if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changeStep)) {
        return;
      }
      if (this.slide.mainSeqStep < this.slide.mainSeqLength) {
        this.slide.nextStep();
      } else {
        if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
          return;
        }
        this.slide.renderSlide(this.currentSlideIndex + 1);
      }
    });
    this.footer.on('prevPage', () => {
      if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
        return;
      }
      if (this.currentSlideIndex > 1) {
        this.slide.renderSlide(this.currentSlideIndex - 1);
      }
    });
    this.footer.on('nextPage', () => {
      if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
        return;
      }
      if (this.currentSlideIndex < this.slideCount) {
        this.slide.renderSlide(this.currentSlideIndex + 1);
      }
    });
    this.footer.on('sideBarToggle', () => {
      if (!this.permissions.hasPermission(ForgeSlidePermissionFlag.changePage)) {
        return;
      }
      if (this.sideBar.isShowSideBar) {
        this.sideBar.hidden();
      } else {
        this.sideBar.show();
      }
    });

    this.rootView.appendChild(this.contentContainer);

    this.emitter.on('renderStart', pageIndex => {
      this.footer.prevPageState(pageIndex !== 0);
    });

    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const that = this;
    Object.defineProperty(this.emitter, 'view', {
      get(): any {
        return that.rootView;
      },
    });
    Object.defineProperty(this.emitter, 'permissions', {
      get(): any {
        return that.permissions;
      },
    });
    Object.defineProperty(this.emitter, 'footView', {
      get(): any {
        return that.footer.root;
      },
    });
    Object.defineProperty(this.emitter, 'sidebarView', {
      get(): any {
        return that.sideBar.root;
      },
    });
    Object.defineProperty(this.emitter, 'pageIndex', {
      get(): any {
        return that.currentSlideIndex;
      },
    });
    Object.defineProperty(this.emitter, 'pageCount', {
      get(): any {
        return that.slideCount;
      },
    });
    Object.defineProperty(this.emitter, 'goto', {
      writable: false,
      enumerable: false,
      value: (pageIndex: number) => {
        this.sideBar.emit('pageChange', pageIndex);
      },
    });
    Object.defineProperty(this.emitter, 'nextStep', {
      writable: false,
      enumerable: false,
      value: () => {
        this.footer.emit('nextStep');
      },
    });
    Object.defineProperty(this.emitter, 'prevStep', {
      writable: false,
      enumerable: false,
      value: () => {
        this.footer.emit('prevStep');
      },
    });
    Object.defineProperty(this.emitter, 'nextPage', {
      writable: false,
      enumerable: false,
      value: () => {
        this.footer.emit('nextPage');
      },
    });
    Object.defineProperty(this.emitter, 'prevPage', {
      writable: false,
      enumerable: false,
      value: () => {
        this.footer.emit('prevPage');
      },
    });
    Object.defineProperty(this.emitter, 'sideBarToggle', {
      writable: false,
      enumerable: false,
      value: () => {
        this.footer.emit('sideBarToggle');
      },
    });
    Object.defineProperty(this.emitter, 'imgContent', {
      writable: false,
      enumerable: false,
      value: (pageIndex: number) => {
        return this.getImageContent(pageIndex);
      },
    });
    Object.defineProperty(this.emitter, 'imgUrl', {
      writable: false,
      enumerable: false,
      value: (pageIndex: number) => {
        return this.getImageUrl(pageIndex);
      },
    });
    Object.defineProperty(this.emitter, 'imgSize', {
      writable: false,
      enumerable: false,
      value: (pageIndex: number) => {
        return this.getImageSize(pageIndex);
      },
    });
    this.applySlideState();
  }

  private getPreviewImageUrl (pageIndex: number) {
    if (pageIndex < 1 || pageIndex > this.slideCount) {
      throw new Error('pageIndex out of range');
    }
    return `${this.prefix}/${this.taskId}/preview/${pageIndex}.png`;
  }

  private async getImageUrl(pageIndex: number) {
    return this.getPreviewImageUrl(pageIndex);
  }

  private async getImageSize(pageIndex: number) {
    const imageUrl = this.getPreviewImageUrl(pageIndex);
    let preview: PreviewImage | null = null;
    try {
      const result = await kvStore.getItem(imageUrl);
      preview = result ? JSON.parse(result) : null;
    } catch (e) {
      console.warn('kvStore getItem error', e);
    }
    if (preview) {
      return { width: preview.width, height: preview.height };
    }
    preview = await this.sideBar.getPreviewImage(imageUrl);
    await kvStore.setItem(imageUrl, JSON.stringify(preview));
    return { width: preview.width, height: preview.height };
  }

  private async getImageContent(pageIndex: number) {
    const imageUrl = this.getPreviewImageUrl(pageIndex);
    let preview: PreviewImage | null = null;
    try {
      const result = await kvStore.getItem(imageUrl);
      preview = result ? JSON.parse(result) : null;
    } catch (e) {
      console.warn('kvStore getItem error', e);
    }
    if (preview) {
      return preview.src;
    }
    preview = await this.sideBar.getPreviewImage(imageUrl);
    await kvStore.setItem(imageUrl, JSON.stringify(preview));
    return preview.src;
  }

  private nextTick = () => {
    this.isSyncing = false;
    requestAnimationFrame(() => {
      this.applySlideState().catch((error) => {
        console.error('Error in applySlideState:', error);
      });
    });
  };

  private applySlideState = async () => {
    if (this.isSyncing) {
      return;
    }

    const lastSyncMessage = this.syncMessageQueue.pop();
    if (!lastSyncMessage) {
      return this.nextTick();
    }

    this.syncMessageQueue = [];
    this.isSyncing = true;
    const { state, dispatch } = lastSyncMessage;
    let ignoreKeys: string[] | undefined = undefined;
    if ( dispatch.type === 'mediaPlay' || dispatch.type === 'mediaPause' || dispatch.type === 'mediaFullscreen') {
      ignoreKeys = [dispatch.id];
    }
    if (this.slide.slideState.currentSlideIndex < 0 || state.currentSlideIndex < 0) {
      // @ts-ignore
      await this.slide.receiveSyncHandler(dispatch);
      return this.nextTick();
    } else if (!deepEqual(this.slide.slideState, state, ignoreKeys)) {
      await this.slide.setSlideState(state);
      // @ts-ignore
      await this.slide.receiveSyncHandler(dispatch);
    } else {
      this.slide.emit(SLIDE_EVENTS.syncReceive, dispatch);
    }
    return this.nextTick();

  };

  private onSlideEventHandler = async (event: Y.YMapEvent<any>) => {
    for (const [key, value] of event.changes.keys.entries()) {
      if (key === 'syncSlide') {
        if (value.action === 'add' || value.action === 'update') {
          const { slideState: slideStateFromServer, dispatch: dispatchFromServer } = this.getMap(this.name).get('syncSlide');
          if (this.lastDispatchUuid === dispatchFromServer.uuid) {
            return;
          }
          this.lastDispatchUuid = dispatchFromServer.uuid;
          this.syncMessageQueue.push({
            state: slideStateFromServer,
            dispatch: dispatchFromServer,
          });
          this.applySlideState();
        }
      }
    }
  };

  private keyBoardEvents = (event: KeyboardEvent) => {
    if (event.key === 'ArrowLeft') {
      this.footer.emit('prevStep');
    } else if (event.key === 'ArrowRight') {
      this.footer.emit('nextStep');
    } else if (event.key === 'ArrowUp') {
      this.footer.emit('prevPage');
    } else if (event.key === 'ArrowDown') {
      this.footer.emit('nextPage');
    }
  };

  private bindKeyBoardEvent() {
    document.addEventListener('keydown', this.keyBoardEvents);
  }

  private unbindKeyBoardEvent() {
    document.removeEventListener('keydown', this.keyBoardEvents);
  }

  private async onFocusInstance() {
    this.bindKeyBoardEvent();
    // await slidePool.active(this.appId, this.slide);
  }

  private onRefocusInstance() {
    this.unbindKeyBoardEvent();
  }


  public async initialize(option: SlideApplicationOption): Promise<void> {
    this.prefix = option.prefix;
    this.taskId = option.taskId;
    const whiteboardApp = new WhiteboardApplication();
    // @ts-ignore
    whiteboardApp.appDoc = this.appDoc;
    // @ts-ignore
    whiteboardApp.appId = `${this.appId}_wb`;
    // @ts-ignore
    whiteboardApp.userId = this.userId;
    // @ts-ignore
    whiteboardApp.userManager = this.userManager;
    // @ts-ignore
    whiteboardApp.deleteSubDoc = this.deleteSubDoc;

    const json = await fetch(`${option.prefix}/${option.taskId}/jsonOutput/slide-1.json`).then(res => res.json());
    this.slideCount = json.slideCount;
    this.footer.setTotalPageIndex(this.slideCount);
    await whiteboardApp.initialize({
      width: json.width,
      height: json.height,
      maxScaleRatio: 1,
    });
    this.whiteboardApp = whiteboardApp;
    this.whiteboard = whiteboardApp.emitter;
    this.whiteboard.enableCameraBoundaryHighlight = false;
    this.whiteboard.enableCameraByMouse = false;
    this.whiteboard.enableCameraByTouch = false;
    this.whiteboard.view.style.width = '100%';
    this.whiteboard.view.style.height = '100%';
    this.whiteboard.view.style.position = 'absolute';
    this.whiteboard.view.style.top = '0';
    this.whiteboard.view.style.left = '0';
    this.whiteboard.view.style.zIndex = '4';
    this.whiteboard.view.classList.add('slide-whiteboard');
    this.whiteboard.permissions.addPermission(WhiteboardPermissionFlag.all);
    this.whiteboard.setCanvasBackgroundColor('#f0f0f000');
    this.whiteboardContainer.style.position = 'relative';
    this.whiteboardContainer.style.flex = '0 0 auto';
    this.whiteboardContainer.style.height = 'calc(100% - 24px)';
    this.whiteboardContainer.classList.add('forge-slide-whiteboard-container');
    this.whiteboardContainer.appendChild(this.whiteboard.view);
    this.whiteboardContainer.appendChild(this.slideContainer);

    this.contentContainer.appendChild(this.whiteboardContainer);
    this.contentContainer.appendChild(this.footer.root);
    this.contentContainer.appendChild(this.sideBar.root);

    if (option.inheritWhiteboardId) {
      whiteboardApp.linkToWhiteboard(option.inheritWhiteboardId);
    }

    this.whiteboard.setViewModeToMain();


    this.slideContainer.setAttribute('builder', 'slide-builder');

    // await slidePool.waitUntilReady(this.appId);
    this.slide = new Slide({
      ...option.options,
      interactive: true,
      anchor: this.slideContainer,
      mode: 'interactive',
      clientId: Math.random().toString(36).substring(2, 15),
      timestamp: () => {
        console.log('timestamp');
        console.log(Date.now, this.calibrationTimestamp, Date.now() - this.calibrationTimestamp);
        return this.calibrationTimestamp;
      },
      // logger: {
      //     info: console.log,
      //     warn: console.warn,
      //     error: console.error,
      // },
    });
    this.slide.setResource(option.taskId, option.prefix);
    this.sideBar.initialize(json.slideCount, option);

    this.slide.on(SLIDE_EVENTS.syncDispatch, event => {
      this.getMap(this.name).set('syncSlide', {
        slideState: this.slide.slideState,
        dispatch: event,
      });
    });

    this.slide.on(SLIDE_EVENTS.mainSeqStepStart, (animateIndex: number) => {
      this.emitter.emit('mainSeqStepStart', animateIndex);
      this.footer.changeIconToPause();
    });

    this.slide.on(SLIDE_EVENTS.mainSeqStepEnd, (animateIndex: number) => {
      this.emitter.emit('mainSeqStepEnd', animateIndex);
      this.footer.changeIconToNextStep();
    });

    this.slide.on(SLIDE_EVENTS.renderError, ({ error, index }) => {
      if (error.errorType === 'CANVAS_CRASH') {
        this.slide.renderSlide(index);
      }
    });

    this.slide.on(SLIDE_EVENTS.renderStart, (slideIndex) => {
      if (slideIndex >= 0) {
        this.sideBar.pauseGetPreviewImageSchedule();
        this.whiteboardApp.emitter.view.style.opacity = '0';
        this.whiteboardApp.emitter.addPage(`${slideIndex}`);
        this.whiteboardApp.emitter.gotoPage(`${slideIndex}`);
        this.sideBar.hidden();
        this.footer.changeIconToPause();
        this.emitter.emit('renderStart', slideIndex);
      }
    });

    this.slide.on(SLIDE_EVENTS.renderEnd, (slideIndex) => {
      if (slideIndex >= 0) {
        this.sideBar.startGetPreviewImageSchedule();
        this.currentSlideIndex = slideIndex;
        this.whiteboardApp.emitter.view.style.opacity = '1';
        this.footer.setCurrentPageIndex(slideIndex);
        // slidePool.active(this.appId, this.slide);
        // slidePool.onRenderEnd(this.appId, this.window?.focused ?? false);
        this.footer.changeIconToNextStep();
        this.emitter.emit('renderEnd', slideIndex);
      }
    });

    this.slide.on(SLIDE_EVENTS.stateChange, (state) => {
      this.getMap(this.name).set('slideState', state);
    });

    this.getMap(this.name).observe(this.onSlideEventHandler);

    (window as any).slide = this.slide;
    (window as any).slideWhiteboard = this.whiteboardApp;
    (window as any).forgeSlide = this;

    this.whiteboardContainer.addEventListener('click', () => {
      this.sideBar.hidden();
    }, false);

    const syncSlide = this.getMap(this.name).get('slideState');
    if (syncSlide && syncSlide.taskId === option.taskId) {
      this.slide.setSlideState(syncSlide);
    } else {
      this.slide.renderSlide(1);
    }
    this.permissions = new ForgeSlidePermissions(this.userManager, (userId: string) => {
      return this.userMap(userId);
    });
    this.permissions.on('change', (userId, flags, value) => {
      this.emitter.emit('permissionChange', userId, flags, value);
      if (this.userId === userId) {
        if (flags.includes(ForgeSlidePermissionFlag.clickAnim)) {
          this.slideContainer.style.pointerEvents = 'auto';
        } else {
          this.slideContainer.style.pointerEvents = 'none';
        }
      }
    });
    this.permissions.setPermission(ForgeSlidePermissionFlag.all);
    this.whiteboardApp.disableViewModel();
    if (this.window) {
      let prevStatus = 'normal';
      this.window.on('statusChange', (status) => {
        if (prevStatus === 'minimized') {
          this.onFocusInstance();
          prevStatus = status;
          return;
        }
        prevStatus = status;
        if (status === 'normal') {
          this.onFocusInstance();
        } else if (status === 'minimized') {
          this.onRefocusInstance();
        }
      });
      this.window.on('focusedChange', (status) => {
        if (status) {
          this.onFocusInstance();
        } else {
          this.onRefocusInstance();
        }
      });
      this.bindKeyBoardEvent();
    }
    // @ts-ignore
    window.__forge_slide = this;
    // @ts-ignore
    window.slidePermissions = this.permissions;
    return Promise.resolve(undefined);
  }

  private userMap(userId: string): Y.Map<any> {
    return this.getMap(`user/${userId}`);
  }

  public async dispose(removeSubDoc: boolean): Promise<void> {
    if (removeSubDoc) {
      this.deleteSubDoc(this.appId);
    }
    await this.whiteboardApp.dispose(removeSubDoc);
    this.rootView.parentElement?.removeChild(this.rootView);
    this.slide.destroy();
    this.sideBar.dispose();
    this.getMap(this.name).unobserve(this.onSlideEventHandler);
    this.permissions.dispose();
    this.footer.dispose();
    // slidePool.remove(this.appId);
  }

}
