import React, { useState } from "react";
import { omitBy, isUndefined, pick, isEqualWith } from "lodash-es";
import {
  ControllerId,
  UID,
  PID,
  SID,
  Word,
  EmptyUnfixPID,
  AlibabaEmployee,
  Speaker,
  CommonUseSpeaker,
  Controller,
  Selection,
  AddUseSpeakerResult,
} from "@tingwu/core";
import { Eventer, useSyncEffect } from "@tingwu/common";
import type {
  BaseExtension,
  BasicRenderParagraphHeadingExtraParams,
  Size,
  GetParagraphTopHeightParams,
  RenderScrollToBottomButtonParams,
} from "../extensions";
import { PlayingWordController } from "./playingWordController";
import { AiAssistantController, QnA, QnAController } from "@tingwu/sphere";
import { defaultTheme, Theme } from "../theme";

export interface ViewEventMap {
  willEditSpeaker: {
    pid?: PID;
    emptyUnfix?: boolean;
    oldSpeaker: string;
  };
  didEditSpeaker: {
    pid?: PID;
    emptyUnfix?: boolean;
    oldSpeaker: string;
    newSpeaker: string;
    isMutiple: boolean;
  };
  toggleMarkParagraph: {
    pid?: PID;
    emptyUnfix?: boolean;
    isMark: boolean; // isMark true 打标 false 取消打标
    markColor: "blue" | "yellow" | "red" | "";
    isParagraphMark: boolean; // isMark true 段落打标 false 局部打标
  };
  userEditParagraph: {
    pid: PID;
    oldText: string;
    newText: string;
  };
  // 回到最新内容-实时
  clickScrollToBottom: {};
  // 回到当前播放位置-离线
  clickReturnToPlayingWord: {};
  // 收藏发言人成为常用发言人
  collectSpeaker: CommonUseSpeaker;
  // 取消收藏发言人成为常用发言人/删除常用发言人
  cancelCollectSpeaker: CommonUseSpeaker;
  selectCommonUseSpeaker: CommonUseSpeaker;
  test: any;
  // 30s无内容检测提示开启
  detectEmptyContentOpen: {};
  // 30s无内容检测弹窗关闭
  detectEmptyContentClose: {};
  clickNoWordHelp: {};
  // 回到顶部-离线
  clickBackToTop: {};
  // 分享纪要内容
  shareContents: {};
}

export interface TranscriptionHooks {
  getTranscriptionContainer?: () => HTMLElement;
  getTranscriptionDocument?: () => HTMLElement;
  onFetchAlibabaEmployees?: (keyword: string) => Promise<AlibabaEmployee[]>;
  getAvailableSpeakers?: () => Speaker[];
  setAvailableSpeakers?: (speakers: Speaker[]) => void;
  renderAvatar?: (uid: UID, size?: number) => React.ReactNode;
  enableSelectAlibabaEmployee?: boolean;
  enableCommonUseSpeaker?: boolean; // 是否启用常用发言人
  enableShowSpeaker?: boolean;
  enableShowFirsttime?: boolean;
  enableEditSpeaker?: boolean;
  getDefaultSpeakerName?: (speaker?: Speaker) => string;
  getDefaultSpeakerAvatar?: (speaker?: Speaker) => string;
  enableOfflineEditWordTip?: boolean;
  onFinishEnableOfflineEditWordTip?: () => void;
  onEvent?: <K extends keyof ViewEventMap>(type: K, event: ViewEventMap[K]) => void;
  getCommonUseSpeakers?: () => CommonUseSpeaker[]; // 常用发言人
  setCommonUseSpeakers?: (speakers: CommonUseSpeaker[]) => void;
  addCommonUseSpeaker?: (speaker: CommonUseSpeaker) => Promise<AddUseSpeakerResult>; // 等待server通知是否成功增加常用发言人
  renderCommonUseAvatar?: (speaker: CommonUseSpeaker) => React.ReactNode;
  qnaController?: QnAController;
  aiAssistantController?: AiAssistantController;
  onDeleteQnas?: (qnas: QnA[]) => void;
  theme?: Partial<Theme>;
  beforeCoverHeight?: number;
  type?: string;
}

type TranscriptionHooksKeys = Array<keyof TranscriptionHooks>;

type RequiredKey =
  | "enableSelectAlibabaEmployee"
  | "enableCommonUseSpeaker"
  | "enableShowSpeaker"
  | "enableEditSpeaker"
  | "getDefaultSpeakerName"
  | "getDefaultSpeakerAvatar"
  | "theme";

type RequiredValue = Required<Pick<TranscriptionHooks, RequiredKey>>;

export type TranscriptionHooksValue = Omit<TranscriptionHooks, RequiredKey> & RequiredValue;

const defaultTranscriptionHooks: RequiredValue = {
  enableSelectAlibabaEmployee: false,
  enableCommonUseSpeaker: true, // 默认启用
  enableShowSpeaker: true,
  enableEditSpeaker: true,
  getDefaultSpeakerName: () => "发言人",
  getDefaultSpeakerAvatar: () =>
    "https://img.alicdn.com/imgextra/i3/O1CN01mqaz5k1gzbSjkrFAW_!!6000000004213-2-tps-339-339.png",
  theme: defaultTheme,
};

export interface SimpleRect {
  width: number;
  height: number;
}

type AnyPID = PID | EmptyUnfixPID | undefined;
export interface TranscriptionEventes {
  hooksChange: {};
  speakerChange: {
    uid: UID;
  };
  containerRectChange: SimpleRect;
  miniModeChange: {
    isMiniMode: boolean;
  };
  onDisplayWordShowUp: {
    pid: PID;
    word?: Word;
    isPlaying?: boolean;
    forceDisplay?: boolean;
    seekWithExtContent?: boolean;
  };
  onDropdownShowUp: {
    clientX: number;
    clientY: number;
    rangeRect: DOMRect;
    pids?: PID[];
  };
  onDropdownHideOff: {};
  onTranslateMenuShowUp: {
    clientX: number;
    clientY: number;
    rangeRect: DOMRect;
    isUnfix: boolean;
    pid?: PID;
    sid?: SID;
    startOffset: number;
    endOffset: number;
    content: string;
  };
  onTranslateMenuHideOff: {};
  currentEditingSpeakerPidChange: {
    pid: AnyPID;
  };
  contentRectChange: {
    rect: DOMRectReadOnly;
  };
  needRefresh: {};
  shareContents: {
    content: string;
  };
}

export type TransGetParagraphTopHeightParams = Omit<GetParagraphTopHeightParams, "controller">;

export class TranscriptionController extends Eventer<TranscriptionEventes> {
  private readonly controller: Controller;

  private hooks: TranscriptionHooksValue;

  private containerRect: SimpleRect | undefined;

  private isMiniMode = false;

  private currentEditingSpeakerPid: AnyPID;

  private unfixElement: HTMLDivElement | undefined;

  private isDidShowUnfixSpeakerTip = false;

  private extensions: BaseExtension[] = [];

  public playingWordController: PlayingWordController;

  private cleanups: Array<() => void> = [];

  constructor(controller: Controller, hooks: TranscriptionHooks) {
    super();
    this.controller = controller;
    this.playingWordController = new PlayingWordController();
    this.hooks = Object.assign({}, defaultTranscriptionHooks, omitBy(hooks, isUndefined));
    this.emit("hooksChange", {});
  }

  setHooks(hooks: TranscriptionHooks) {
    this.hooks = Object.assign({}, this.hooks, omitBy(hooks, isUndefined));
    this.emit("hooksChange", {});
  }

  getHooks() {
    return this.hooks;
  }

  setContainerRect(rect: SimpleRect) {
    this.containerRect = rect;
    this.setIsMiniMode(rect.width <= 560);
    this.emit("containerRectChange", rect);
  }

  getContainerRect() {
    return this.containerRect;
  }

  setIsMiniMode(isMiniMode: boolean) {
    if (this.isMiniMode !== isMiniMode) {
      this.isMiniMode = isMiniMode;
      this.emit("miniModeChange", {
        isMiniMode,
      });
    }
  }

  getIsMiniMode() {
    return this.isMiniMode;
  }

  setCurrentEditingSpeakerPid(pid: AnyPID) {
    this.currentEditingSpeakerPid = pid;
    this.emit("currentEditingSpeakerPidChange", { pid });
  }

  getCurrentEditingSpeakerPid() {
    return this.currentEditingSpeakerPid;
  }

  setUnfixElement(unfixElement: HTMLDivElement | undefined) {
    this.unfixElement = unfixElement;
  }

  getUnfixElement() {
    return this.unfixElement;
  }

  setIsDidShowUnfixSpeakerTip(value: boolean) {
    this.isDidShowUnfixSpeakerTip = value;
  }

  getIsDidShowUnfixSpeakerTip() {
    return this.isDidShowUnfixSpeakerTip;
  }

  setExtensions(extensions: BaseExtension[]) {
    this.extensions = extensions;
    this.extensions.forEach(ext => {
      if (ext.init) {
        ext.init({
          controller: this.controller,
        });
      }
      if (ext.providerParagraphsNeedRefresh) {
        const result = ext.providerParagraphsNeedRefresh(this.needRefresh);
        if (result) {
          this.cleanups.push(result);
        }
      }
    });
  }

  needRefresh = () => {
    this.emit("needRefresh", {});
  };

  renderParagraphHeadingExtra(params: BasicRenderParagraphHeadingExtraParams) {
    if (this.extensions.length === 0) {
      return undefined;
    }

    const passParams = {
      ...params,
      controller: this.controller,
    };
    const result: React.ReactNode[] = [];
    this.extensions.forEach(e => {
      if (e.renderParagraphHeadingExtra) {
        const elem = e.renderParagraphHeadingExtra(passParams);
        if (elem) {
          result.push(elem);
        }
      }
    });
    return result;
  }

  renderUnfixMask(params: { onClick?: () => void }) {
    const result: React.ReactNode[] = [];
    this.extensions.forEach(e => {
      if (e.renderUnfixMask) {
        const elem = e.renderUnfixMask(params);
        if (elem) {
          result.push(elem);
        }
      }
    });

    return result.length ? result : undefined;
  }

  getIsRenderUnfixSpeakerPlaceholder() {
    if (this.extensions.length === 0) {
      return true;
    }
    return this.extensions.some(e => {
      if (!e.getIsRenderUnfixSpeakerPlaceholder) {
        return true;
      }
      return e.getIsRenderUnfixSpeakerPlaceholder();
    });
  }

  renderSelectionContextMenu(params: { currentSelection: Selection }) {
    let renderResult: React.ReactNode | undefined;
    const passParams = {
      ...params,
      controller: this.controller,
    };
    this.extensions.some(ext => {
      if (ext.renderSelectionContextMenu) {
        renderResult = ext.renderSelectionContextMenu(passParams);
        if (renderResult) {
          return true;
        }
      }
      return false;
    });

    return renderResult;
  }

  getContextMenuSize() {
    let size: Size | undefined;
    this.extensions.some(ext => {
      if (ext.getContextMenuSize) {
        size = ext.getContextMenuSize();
        if (size) {
          return true;
        }
      }
      return false;
    });
    return size;
  }

  isHasParagraphTop(params: TransGetParagraphTopHeightParams) {
    const props = {
      ...params,
      controller: this.controller,
    };
    return this.extensions.some(ext => {
      if (ext.isHasParagraphTop) {
        return ext.isHasParagraphTop(props);
      }
      return false;
    });
  }

  getParagraphTopHeight(params: TransGetParagraphTopHeightParams) {
    const props = {
      ...params,
      controller: this.controller,
    };
    let height = 0;
    this.extensions.forEach(ext => {
      if (ext.getParagraphTopHeight) {
        height += ext.getParagraphTopHeight(props);
      }
    });
    return height;
  }

  renderParagraphTop(params: TransGetParagraphTopHeightParams) {
    const props = {
      ...params,
      controller: this.controller,
    };
    const renderElements: React.ReactNode[] = [];
    this.extensions.forEach(ext => {
      if (ext.renderParagraphTop) {
        renderElements.push(ext.renderParagraphTop(props));
      }
    });
    return renderElements;
  }

  renderScrollToBottomButton(params: Omit<RenderScrollToBottomButtonParams, "controller">) {
    const props = {
      ...params,
      controller: this.controller,
    };
    const renderElements: React.ReactNode[] = [];
    this.extensions.forEach(ext => {
      if (ext.renderScrollToBottomButton) {
        renderElements.push(ext.renderScrollToBottomButton(props));
      }
    });
    return renderElements;
  }

  getIsShowTranslatePopup() {
    let isShow = true;
    this.extensions.forEach(ext => {
      if (ext.getIsShowTranslatePopup) {
        isShow &&= ext.getIsShowTranslatePopup();
      }
    });
    return isShow;
  }

  clean() {
    this.cleanups.forEach(cleanup => {
      cleanup();
    });
    this.cleanups = [];
  }
}

const controllerMap: Record<ControllerId, TranscriptionController> = {};

export const generateTranscriptionController = (controller: Controller) => {
  const { id } = controller;
  if (controllerMap[id] == null) {
    controllerMap[id] = new TranscriptionController(controller, {});
  }
  return controllerMap[id];
};

export const removeTranscriptionController = (id: ControllerId) => {
  delete controllerMap[id];
};

export const getTranscriptionController = (controller: Controller) => {
  const { id } = controller;
  if (!controllerMap[id]) {
    controllerMap[id] = new TranscriptionController(controller, defaultTranscriptionHooks);
  }
  return controllerMap[id];
};

export const useTranscriptionHooks = (
  controller: Controller,
  deps: TranscriptionHooksKeys = []
) => {
  const transController = getTranscriptionController(controller);
  const [result, setResult] = useState(() => pick(transController.getHooks(), deps));

  useSyncEffect(() => {
    return transController.on("hooksChange", () => {
      const newHooks = pick(transController.getHooks(), deps);
      if (!isEqualWith(result, newHooks, (a, b) => a === b)) {
        setResult(newHooks);
      }
    });
  }, [controller.id, transController, deps, result]);

  return result;
};
