/* eslint-disable @typescript-eslint/indent */
import React from "react";
import { Model, BookEventMap } from "../model";
import { Selection, Point, Range } from "../editor";
import { Eventer, getWordsCount, CommonRange } from "@tingwujs/common";
import { CustomMeetingController } from "./customMeetingController";
import { SearchReplaceController } from "./searchReplaceController";
import { VoiceWordController } from "./voiceWordController";
import {
  TranslateController,
  TranslateMode,
  FixParagraphTranslateTranslate,
} from "./translateController";
import {
  FixParagraphTextPolish,
  TextPolishController,
  TextPolishMode,
  TextPolishResult,
} from "./textPolishController";
import { AgendaController, Agenda, AgendaCheckpoint } from "./agendaController";
import { ParagraphFilterController, FilterOption } from "./paragraphFilterController";
import { UnfixToFixedController } from "./unfixToFixedController";
import {
  SpeakerController,
  AlibabaEmployee,
  Speaker,
  SpeakerGetter,
  SpeakerSetter,
  DefaultSpeakerNameGetter,
  DefaultSpeakerAvatarGetter,
  CommonUseSpeaker,
  CommonUseSpeakerGetter,
  CommonUseSpeakerSetter,
  AddUseSpeakerResult,
  RenderAvatarGetter,
} from "./speakerController";
import {
  TranModel,
  PID,
  WID,
  UID,
  Word,
  P,
  Paragraph,
  SC,
  SID,
  TranslateLanguage,
} from "../persist";
import { Reader, createReader } from "./reader";
import { Setter, createSetter } from "./setter";
import { UndoRedoStacker } from "./model";
import { getPreviousPid, generateControllerId, generatePid, report } from "../utils";
import { createParagraphChangeSpecificHandler } from "./event";
import { ControllerId, OldSelection } from "./type";
import { cloneDeep, isEqual } from "lodash-es";
import { CaptionCmdController } from "./captionCmdController";
import { ExtractController, ExtractContent } from "./extractController";
import { TodoList, TodoListCheckpoint, TodoListController } from "./todoListController";
import { GetEditorHandler, SelectionController } from "./selectionController";
import { TextBoxController } from "./textBoxController";
import { getEffectKeys, getParagraphRanges } from "../utils/editor";

export type {
  OldSelection,
  ControllerId,
  FilterOption,
  Agenda,
  AlibabaEmployee,
  Speaker,
  CommonUseSpeaker,
  AddUseSpeakerResult,
  ExtractContent,
};

export { TextPolishMode, TranslateMode };

interface SelectionParamsType {
  selection?: Selection | OldSelection;
}

// 特殊情况
// 1. click pause btn
// 2. has unfix paragraph
// 3. fix && stash_text为空 -> 不显示蓝色气泡
// 4. 增加stash_text -> 显示蓝色气泡
// 5. fix
// 6. complete -> 不显示蓝色气泡
export enum TranscriptingState {
  // 打开时会显示一个蓝色起泡
  // 如果最后一个段落是unfix段落，则将此段落显示为蓝色起泡
  // 如果所有段落都fix，此会显示一个空白的蓝色起泡，不管是否有正在识别中的内容
  Enabled,

  // 如果最后一个段落是unfix段落，则将此段落显示为蓝色起泡
  // 如果所有段落都fix，当正在识别中有内容时，则会显示一个空白的蓝色起泡，否则则不显示
  Disabing,

  // 不会显示蓝色起泡
  Disabled,
}

export interface Eventes {
  readonlyChange: {
    readonly: boolean;
  };
  defaultSpeakerReadonlyChange: {
    defaultSpeakerReadonly: boolean;
  };
  editorFocusChange: {
    pid: PID | undefined;
  };
  paragraphChange: {
    pid: PID;
    wid?: WID;
    typeChange?: string; // 拓展 改变文段的类型
    tempDeleteWord?: object; // 删除的词内容
    oldParagraph?: Paragraph; // 修改前的段落
  };
  paragraphListChange: {
    maybeChangedPids: PID[];
  };
  bookParagraphChangeSpecific: BookEventMap["paragraphChangeSpecific"];
  paragraphChangeSpecific: {
    addParagraphPids: PID[];
    removeParagraphPids: PID[];
    updateParagraphPids: PID[];
    removeParagraphs?: Record<PID, Paragraph>;
    updateParagraphUserChangedPids: PID[];
    unfixToFixedPids: PID[];
    userFixedPids: PID[];
    textNotChangePids?: PID[];
    userChangePids?: PID[];
  };
  paragraphFilterChange: {};
  emptyUnfixParagraphShowUp: {};
  transcriptingStateChange: {
    transcriptingState: TranscriptingState;
  };
  emptyUnfixSpeakerChange: {};
  emptyUnfixParagraphChange: {};
  unfixSentenseChange: {
    text: string;
  };
  unfixContentChange: {
    pid?: PID;
    fixWords: Word[];
    unfixContent: string;
  };
  addFixParagraph: {};
  livingModeChange: {
    livingMode: boolean;
  };

  realTimeLivingChange: {
    livingMode: boolean;
  };
  livingFixWord: boolean;
  deleteSingleWord: boolean;
  searchReplaceChange: {
    keyword: string;
    activeIndex: number;
    foundLength: number;
    selections: OldSelection[];
    enableScroll: boolean;
  };
  playingVoiceWordEnableChange: {
    enable: boolean;
  };
  playingVoiceWordChange: {
    pid: PID;
    wid?: WID;
    word: Word | undefined;
    forceDisplay?: boolean;
    seekWithExtContent?: boolean;
    isPlaying?: boolean;
  };
  needStartPlayVoice: {
    words: Word[];
  };
  selectionChange: {
    selection: Selection | undefined;
  };
  selectionStylesChange: {
    styles: Record<PID, React.CSSProperties[]>;
  };
  caretStyleChange: {
    value?: {
      pid: PID;
      style: React.CSSProperties;
      editorRect: DOMRect;
    };
  };
  mouseUpSelectionChange: SelectionParamsType;
  blurCurrentTextEditor: {};
  extraContents: {
    contents: string[];
    type: string;
    pid: PID;
    sid?: SID;
    wid?: WID;
    paragraphContents?: Array<{ pid: PID; wid: WID; text: string }>;
  };
  markWordsChanged: {
    markedContinuousWords: Word[][];
  };
  fixParagraphTranslateChange: {
    pid: PID;
    translate: FixParagraphTranslateTranslate;
  };
  unfixParagraphTranslateChange: {
    pid?: PID;
    fixContentTranslate: Word[];
    unfixContentTranslate: string;
  };
  translateModeChange: {
    translateMode: TranslateMode;
  };
  // **** 【AI 改写】START ****
  // AI 改写模式变更监听
  textPolishModeChange: {
    textPolishMode: TextPolishMode;
  };
  // AI 改写信息变更监听
  fixParagraphTextPolishChange: {
    pid: PID;
    textPolish: FixParagraphTextPolish;
  };
  // **** 【AI 改写】 END****
  paragraphNeedTranslate: {
    pid: PID;
  };
  agendasChange: {
    agendas: Agenda[];
  };
  activeAgendaChange: {
    agenda?: Agenda;
  };
  meetingActiveAgendaChange: {
    pid: PID;
  };
  detectedFirstShowUpAgenda: {
    agenda: Agenda;
  };
  todoListChange: {
    todoLists: TodoList[];
  };
  activetodoListChange: {
    todoList?: TodoList;
  };

  detectedFirstShowUpTodoList: {
    todoList: TodoList;
  };

  speakerNameChange: {
    uid: UID;
  };
  saveDocument: {};
  modelValueChange: {};
  test: any;
  uglyUserManualAddParagraph: {};
  updateShortcutPositioningButtonBottom: { num: number };
  report: {
    type: "exception";
    message?: string;
    payload?: any;
  };
  detectEmptyContent: {};
  closeNoRecordTip: {};
  detectSpeakerFull: {};
  captionChange: {};
  needSeekToWord: {
    word: Word;
  };
  shareContents: {
    content: string;
    speakerName: string;
    avatarUrl: string;
    speakerTime: number;
  };
  caretPendingChange: {
    caretPending: boolean;
  };
  renderingParagraph: {
    pid: PID;
  };
  finishRenderParagraph: {
    pid: PID;
  };
  focusEditor: {};

  liveMeetingInfoChange: {
    meetingName?: string; // 名称
    meetingSpeaker?: string; // 发言人信息
    meetingLabInfo?: string; // 直播大模型相关信息
  };
  liveMeetingStatusChange: {
    notLogin?: boolean; // 未登录
    isInvalid?: boolean; // 无效
    isCompleted?: boolean; // 是否结束
  };
  liveMeetingPermissionChange: {
    isDenied?: boolean; // 没有权限(被踢、人数超限)
  };
  liveMeetingHistoryLoad: {
    hasHistory?: boolean;
  };
}

export class Controller extends Eventer<Eventes> {
  public readonly id: ControllerId;

  public readonly reader: Reader;

  public readonly setter: Setter;

  private readonly runningOnServer: boolean;

  model: Model; // 放开为公共属性

  public customMeetingController: CustomMeetingController;

  private readonly searchReplaceController: SearchReplaceController;

  private readonly voiceWordController: VoiceWordController;

  private readonly paragraphFilterController: ParagraphFilterController;

  private readonly translateController: TranslateController;

  private readonly textPolishController: TextPolishController; // AI原文改写控制

  private readonly agendaController: AgendaController;

  private readonly todoListController: TodoListController;

  private readonly speakerController: SpeakerController;

  private readonly unfixToFixedController: UnfixToFixedController;

  private readonly captionCmdController: CaptionCmdController;

  private readonly extractController: ExtractController;

  private readonly selectionController: SelectionController;

  private readonly textBoxController: TextBoxController;

  private readonly undoRedoStacker: UndoRedoStacker;

  private livingMode = false;

  private editorCutData = false;

  private realTimelivingMode = false;

  private transcriptingState: TranscriptingState = TranscriptingState.Disabled;

  private readonly unfixChangedWaiting: Record<PID, boolean> = {};

  private lastUnfixPid: PID | undefined;

  private userFixedPid: PID | undefined;

  private tempUid?: string;

  private tempMark?: string;

  private holdUnfixContent = "";

  private editorFocusPid: PID | undefined;

  private readonly = false;

  private defaultSpeakerReadonly = false; // 默认发言人是否只读

  private noSingleWord = false; // fix 区域删除词的某字，但是未删除词全部

  private audioLoading = false; // 音频是否加载中

  private showHeaderTime = true; // 是否显示header上的时间

  // 实时模式unfix区编辑文字数超过下列数量时会主动成段
  private unfixMaxWords = 160;
  fileModel: string;
  reviewHeight: any = 0;
  language: TranslateLanguage | undefined;

  constructor(runningOnServer = false, fileModel = "file") {
    super();
    this.id = generateControllerId();
    this.runningOnServer = runningOnServer;
    this.fileModel = fileModel;
    this.model = new Model();
    this.undoRedoStacker = new UndoRedoStacker(this.model, {
      restoreSelection: s => {
        this.handleSelectionChange(s);
      },
    });

    this.reader = createReader(this.model);
    this.setter = createSetter(this, this.model, {
      beforeApply: (_isSilent, extPayload) => {
        const { userFixedPid } = extPayload || {};
        this.userFixedPid = userFixedPid;
      },
      afterApply: (c, i, nextSelection, isSilent, isPreteat) => {
        report("applyOps", c);
        if (isSilent) {
          return;
        }
        if (c.length === 0) {
          return;
        }
        if (isPreteat) {
          this.undoRedoStacker.preteat(c, i, this.getCurrentSelection());
        } else {
          this.undoRedoStacker.stash(c, i, this.getCurrentSelection(), nextSelection);
        }
        if (nextSelection && !this.getDontSelectNext()) {
          this.handleSelectionChange(nextSelection);
        }
      },
    });

    this.customMeetingController = new CustomMeetingController(this);
    this.searchReplaceController = new SearchReplaceController(this);
    this.voiceWordController = new VoiceWordController(this);
    this.paragraphFilterController = new ParagraphFilterController(this);
    this.translateController = new TranslateController(this);
    this.textPolishController = new TextPolishController(this); // AI 原文改写
    this.agendaController = new AgendaController(this);
    this.todoListController = new TodoListController(this);
    this.speakerController = new SpeakerController(this);
    this.unfixToFixedController = new UnfixToFixedController(this);
    this.captionCmdController = new CaptionCmdController(this);
    this.extractController = new ExtractController(this);
    this.selectionController = new SelectionController(this);
    this.textBoxController = new TextBoxController(this);

    this.listenModelEvents();

    this.on("deleteSingleWord", e => {
      this.noSingleWord = e;
    });
  }

  private currentSelection: Selection | undefined;
  private isDontSelectNext = false;
  private historyModel: TranModel | undefined;

  getReadonly() {
    return this.readonly;
  }

  setReadonly(readonly: boolean) {
    this.readonly = readonly;
    this.emit("readonlyChange", { readonly });
  }

  getDefaultSpeakerReadonly() {
    return this.defaultSpeakerReadonly;
  }

  setDefaultSpeakerReadonly(defaultSpeakerReadonly: boolean) {
    this.defaultSpeakerReadonly = defaultSpeakerReadonly;
    this.emit("defaultSpeakerReadonlyChange", { defaultSpeakerReadonly });
  }

  getEditorFocusPid() {
    return this.editorFocusPid;
  }

  setEditorFocusPid(pid: PID | undefined) {
    this.editorFocusPid = pid;
    this.emit("editorFocusChange", { pid });

    if (pid && pid === this.unfixToFixedController.getLastUnfixPid()) {
      this.unfixToFixedController.setLastUnfixIsFocus(true);
    } else {
      this.unfixToFixedController.clearFocus();
    }
  }

  processParagraphFrames(pg: Array<P & { partial: boolean }>) {
    // 优先过滤一遍消息，将已存在的word，更新一遍
    pg.forEach(item => {
      const { sc } = item;
      const words = this.getWordsBySces(sc);

      const existWords = words.filter(word => {
        return this.getWord(word.wid) && this.isAppearedWid(word.wid);
      });
      if (existWords.length > 0) {
        this.updateExistWords(existWords);
      }
    });

    pg.forEach(item => {
      const { partial, pi, sc, ui } = item;
      const paragraph = this.getParagraph(pi);
      const words = this.getWordsBySces(sc);
      const targetWids = words.map(word => word.wid);
      if (partial) {
        if (!paragraph) {
          this.addUnfixParagraph(pi, ui, targetWids);
        }
        this.appendWords(pi, words);
        // 智能区分发言人场景，检测 unfix 发言人没有的时候，进行新增
        // const unfixPUid = this.getUnfixUid();
        // if (
        //   ui && this.handleAutoSplitSpeaker
        //   && !unfixPUid // 只处理没有 UID 的情况
        //   // && (!unfixPUid || unfixPUid !== ui) // unfix 发言人动态更新
        // ) {
        //   this.addUnfixParagraphUid(pi, ui);
        // }
      } else if (!paragraph) {
        // 没有 unfix paragraph，则新增一个
        this.addUnfixParagraph(pi, ui, targetWids);
        this.appendWords(pi, words, true);
        this.updateWillFixParagraphSpeaker(pi, ui); // 解决自动识别发言人跳动问题
        this.addFixParagraph(pi, targetWids);
      } else {
        this.appendWords(pi, words, true);
        this.updateWillFixParagraphSpeaker(pi, ui); // 解决自动识别发言人跳动问题
        this.addFixParagraph(pi, targetWids);
      }
    });
  }

  dontSelectNext() {
    this.isDontSelectNext = true;
    return this;
  }

  getDontSelectNext() {
    const { isDontSelectNext } = this;
    this.isDontSelectNext = false;
    return isDontSelectNext;
  }

  setCurrentSelection(selection?: Selection) {
    this.currentSelection = selection;
    this.emit("selectionChange", {
      selection,
    });

    if (selection) {
      this.setEditorFocusPid(selection.range.start.pid);
    } else {
      this.setEditorFocusPid(undefined);
    }
  }

  getCurrentSelection() {
    return this.currentSelection;
  }

  setEmptyUnfixTempUid(tempUid?: string) {
    this.tempUid = tempUid;
    this.emit("emptyUnfixSpeakerChange", {});
  }

  setEmptyUnfixTempParagraphMark(tempMark?: string) {
    if (this.tempMark === tempMark) {
      return;
    }
    this.tempMark = tempMark;
    this.emit("emptyUnfixParagraphChange", {});
  }

  getEmptyUnfixTempParagraphMark() {
    return this.tempMark;
  }

  getEmptyUnfixTempUid() {
    return this.tempUid;
  }

  isHasParagraphMarkColor(pid: PID, color = "") {
    const paragraph = this.getParagraph(pid);
    if (!paragraph) {
      return false;
    }
    const { wids } = paragraph;

    let matchTag = color;
    let isSame = wids.length > 0;

    wids.some(wid => {
      const word = this.getWord(wid);
      if (!matchTag) {
        matchTag = word.tag;
      } else if (matchTag !== word.tag) {
        isSame = false;
        return true;
      }
      return false;
    });

    return isSame;
  }

  setLivingMode(livingMode: boolean) {
    this.livingMode = livingMode;

    this.emit("livingModeChange", { livingMode });
  }

  // 设置为 直播模式
  setRealTimeLivingMode(livingMode: boolean) {
    this.realTimelivingMode = livingMode;
    this.emit("realTimeLivingChange", { livingMode });
  }

  setEditorCutData(editorCutData: boolean) {
    this.editorCutData = editorCutData;
  }

  setAudioLoading(audioLoading: boolean) {
    this.audioLoading = audioLoading;
  }

  getAudioLoading() {
    return this.audioLoading;
  }

  getUnfixCutData() {
    return this.editorCutData;
  }

  getRealTimeLivingMode() {
    return this.realTimelivingMode;
  }

  getLivingMode() {
    return this.livingMode;
  }

  setTranscriptingState(transcriptingState: TranscriptingState) {
    this.transcriptingState = transcriptingState;

    if (transcriptingState === TranscriptingState.Disabled) {
      this.setUnfixSentense("");
      this.setUnfixTranslate(undefined, "", []);
    }
    this.emit("transcriptingStateChange", {
      transcriptingState,
    });
  }

  getTranscriptingState() {
    return this.transcriptingState;
  }

  private tempNfixingWids: WID[] = [];

  popTempNfixingWids() {
    const result = this.tempNfixingWids;
    this.tempNfixingWids = [];
    return result;
  }

  pushTempNfixingWids(wids: WID[]) {
    this.tempNfixingWids = wids;
  }

  clearNfixingWids() {
    this.tempNfixingWids = [];
  }

  getUnfixPid() {
    const pids = this.getPids();
    if (pids.length === 0) {
      return undefined;
    }

    const lastPid = pids[pids.length - 1];

    const paragraph = this.getParagraph(lastPid);
    return paragraph.isUnfix ? lastPid : undefined;
  }

  getUnfixUid() {
    const unfixPid = this.getUnfixPid();
    return unfixPid ? this.getParagraph(unfixPid).uid : undefined;
  }

  getUnfixParagraph() {
    const unfixPid = this.getUnfixPid();
    return unfixPid ? this.getParagraph(unfixPid) : undefined;
  }

  // 是否处理自动区分发言人
  get handleAutoSplitSpeaker() {
    const { isAutoSpeaker } = this.customMeetingController;
    return isAutoSpeaker;
  }

  // 变更正在发言的发言人
  changeSpeakingSpeaker(uid?: UID) {
    // 调用 customMeetingController 来实现
    this.customMeetingController.updateSpeakingSpeaker(uid);
  }

  // 存在空白unfix区时，有文字进入，生成unfixParagraph
  // 此操作不进入历史堆栈
  addUnfixParagraph(pid: PID, uid?: UID, targetWids?: WID[], words?: Word[]) {
    report("call addUnfixParagraph", { pid, uid, words });
    if (!this.livingMode) {
      return;
    }

    const paragraph = this.getParagraph(pid);
    if (paragraph && paragraph.isUnfix) {
      this.emit("report", {
        type: "exception",
        message: `Exist pid: '${pid}' found in 'addUnfixParagraph'`,
      });
      return;
    }

    // unfix 不展示发言人
    // 自动区分发言人: 有 uid 需要新增发言人
    // if (uid && this.handleAutoSplitSpeaker) {
    //   this.speakerController.appendAutoSpeaker({ uid, name: '' });
    // }

    let _uid: UID = uid || "";
    if (this.handleAutoSplitSpeaker) {
      // 自动区分发言人： unfix 不添加发言人
      _uid = "";
    } else {
      // 非自动区分发言人: 新增临时 ID
      const tempUid = this.getEmptyUnfixTempUid();
      if (tempUid && tempUid !== _uid) {
        this.unfixChangedWaiting[pid] = true;
        _uid = tempUid;
      }
    }

    this.silent();
    this.setter.book.addUnfixParagraph(pid, _uid, targetWids);

    if (words) {
      const formatedWords = words.map(word => {
        if (!this.tempMark) {
          return word;
        }
        const newWord = cloneDeep(word);
        newWord.tag = this.tempMark;
        return newWord;
      });

      this.setter.book.appendWords(pid, formatedWords);
    }

    this.tempMark = undefined;

    this.emitAsync("addFixParagraph", {});
    this.setEmptyUnfixTempUid();

    this.translateController.setUnfixToFixed(pid);
    this.unfixToFixedController.setLastUnfixPid(pid);
  }

  // 此操作不进入历史堆栈
  // 当前pid必须是unfix段落，才可以调用此方法
  addFixParagraph(pid: PID, targetWids?: WID[]) {
    report("call addFixParagraph", { pid });
    if (!this.livingMode) {
      return;
    }

    const paragraph = this.getParagraph(pid);
    if (!paragraph || !paragraph.isUnfix) {
      this.emit("report", {
        type: "exception",
        message: `Exist pid: '${pid}' found in 'addFixParagraph'`,
      });
      return;
    }

    this.silent();
    this.updateFixedParagraphAutoSpeaker(paragraph.uid);
    console.log("speakerC:updateFixedParagraphAutoSpeaker:pid,uid", pid, paragraph.uid);
    this.setter.book.addFixParagraph(pid, targetWids);
    this.setEmptyUnfixTempUid(); // 清空 unfix 默认发言人
    this.emitAsync("addFixParagraph", {});

    // 成段，清空当前发言人
    this.changeSpeakingSpeaker();

    // 清空unfix区内容
    this.setUnfixTranslateByWords(undefined, "", []);

    // 设置 unfix 默认发言人
    if (
      this.getTranscriptingState() === TranscriptingState.Enabled &&
      !this.getUnfixPid() &&
      !this.handleAutoSplitSpeaker // 自动区分发言人时候，不需要设置
    ) {
      this.emit("emptyUnfixParagraphShowUp", {});
    }
  }

  // 检测并更新即将成段发言人 UID（解决自动识别发言人跳动问题）
  updateWillFixParagraphSpeaker(pid: PID, uid?: UID) {
    console.log(
      "speakerC:updateWillFixParagraphSpeaker1:pid,uid,handleAutoSplitSpeaker",
      pid,
      uid,
      this.handleAutoSplitSpeaker
    );
    if (!uid || !this.handleAutoSplitSpeaker) return;

    const { uid: prevUid } = this.getParagraph(pid);
    console.log("speakerC:updateWillFixParagraphSpeaker2:pUid", prevUid);
    if (!prevUid || prevUid !== uid) {
      this.silent();
      this.updateUserByPid(pid, uid);
    }
  }

  // 补充 Unfix 成句时候，没有 UID 的情况
  addUnfixParagraphUid(uPid: PID, uid: UID) {
    const unfixParagraph = this.getUnfixParagraph();
    if (unfixParagraph && unfixParagraph.pid === uPid) {
      // 更新 unfixParagraph UID
      this.model.book.updateParagraph(unfixParagraph.pid, {
        ...unfixParagraph,
        uid,
      });
      // 追加自动区分发言人
      this.speakerController.appendAutoSpeaker({ uid, name: "" });
    }
  }

  // 成段直接区分多个段落的未走 unfix 时候，会导致自动区分发言人丢失
  updateFixedParagraphAutoSpeaker(uid?: UID) {
    console.log("speakerC:updateFixedParagraphAutoSpeaker:uid", uid);
    if (this.handleAutoSplitSpeaker && uid) {
      this.speakerController.appendAutoSpeaker({ uid, name: "" });
    }
  }

  // 添加句子
  // 此操作不进入历史堆栈
  appendWords(pid: PID, words: Word[], willFix = false) {
    report("call appendWords", { pid, words });
    this.silent();
    this.setter.book.appendWords(pid, words, willFix);
  }

  // 添加句子
  // 此操作不进入历史堆栈
  updateExistWords(words: Word[]) {
    report("call updateExistWords", { words });
    this.silent();
    this.setter.book.updateExistWords(words);
  }

  // 此操作不进入历史堆栈
  setParagraphMark(pid: PID, tagColor: string) {
    report("call setParagraphMark", { pid, tagColor });
    const paragraph = this.getParagraph(pid);
    if (tagColor && this.isHasParagraphMarkColor(pid, tagColor)) {
      return;
    }
    this.silent();
    this.setter.book.setParagraphMark(pid, tagColor);
    if (paragraph.isUnfix) {
      this.unfixChangedWaiting[pid] = true;
    }
    this.emit("markWordsChanged", {
      markedContinuousWords: this.getMarkedContinuousWords(),
    });
  }

  // 修改未成句句子
  setUnfixSentense(text: string) {
    if (this.holdUnfixContent === text) {
      return;
    }
    this.holdUnfixContent = text;
    this.emit("unfixSentenseChange", {
      text,
    });
    this.emitUnfixContentChange();
  }

  // 分享整段纪要
  shareParagraphContents(pid: PID) {
    if (!pid) return;
    const content = this.getTextContent(pid!);

    const data = this.getParagraph(pid);
    const speaker = this.getSpeaker(data.uid!);
    const speakerName = this.renderSpeakerName(data.uid!);
    const avatarUrl = speaker?.avatarUrl || "";
    const speakerTime = this.getWord(data.wids[0])?.beginTime;

    this.emit("shareContents", {
      content,
      speakerName,
      avatarUrl,
      speakerTime,
    });
  }

  // 分享选择内容
  shareSelectContents(selection: Selection) {
    const { range } = selection;
    const startOffset = range.start.offset;
    const endOffset = range.end.offset;
    const { pid } = range.start;
    if (startOffset === endOffset) {
      return;
    }
    const textContent = this.getTextContent(pid);

    const data = this.getParagraph(pid);
    const speakerName = this.renderSpeakerName(data.uid!);
    const speaker = this.getSpeaker(data.uid!);
    const avatarUrl = speaker?.avatarUrl || "";

    const speakerTime = this.getWordByOffset(pid, startOffset)?.beginTime;
    this.emit("shareContents", {
      content: textContent.slice(startOffset, endOffset),
      speakerName,
      avatarUrl,
      speakerTime: speakerTime!,
    });
  }

  getUnfixContent() {
    return this.holdUnfixContent;
  }

  emitUnfixContentChange() {
    const pid = this.getUnfixPid();
    const words = pid ? this.getWords(pid) : [];
    this.emit("unfixContentChange", {
      unfixContent: this.holdUnfixContent,
      fixWords: words,
      pid,
    });
  }

  handleSelectionChange(selection: Selection) {
    this.currentSelection = selection;
    this.emit("selectionChange", {
      selection,
    });
  }

  listenModelEvents() {
    const { book } = this.model;

    book.on("lastUnfixPidChange", ({ pid }) => {
      this.lastUnfixPid = pid;
    });

    book.on("wordChange", ({ pid, wid, userChangeText }) => {
      if (this.editorCutData) {
        // 用户剪切数据， 其中有词组未全部剪切。
        userChangeText = true;
        this.editorCutData = false;
      }
      const _text = userChangeText || this.noSingleWord ? "userFixWord" : undefined;
      this.noSingleWord = false;
      this.emit("paragraphChange", { pid, wid, typeChange: _text });
    });
    book.on("paragraphChange", ({ pid, wid, typeChange = "", tempDeleteWord, oldParagraph }) => {
      let _tem: object | undefined = {};
      if (["deleteWord", "addWord"].includes(typeChange)) {
        _tem = { tempDeleteWord };
      }
      this.emit("paragraphChange", { pid, wid, typeChange, ..._tem, oldParagraph });
    });
    book.on("paragraphListChange", args => {
      this.emit("paragraphListChange", args);
    });

    book.on("paragraphChangeSpecific", args => {
      this.emit("bookParagraphChangeSpecific", args);
    });

    book.on(
      "paragraphChangeSpecific",
      createParagraphChangeSpecificHandler(args => {
        const updateParagraphUserChangedPids: PID[] = [];
        const unfixToFixedPids: PID[] = [];
        const userFixedPids: PID[] = [];

        args.updateParagraphPids.forEach(pid => {
          if (this.lastUnfixPid === pid) {
            this.lastUnfixPid = undefined;
            unfixToFixedPids.push(pid);
            if (this.unfixChangedWaiting[pid]) {
              updateParagraphUserChangedPids.push(pid);
              delete this.unfixChangedWaiting[pid];
            }
          }
        });

        if (this.userFixedPid) {
          userFixedPids.push(this.userFixedPid);
          this.userFixedPid = undefined;
        }
        this.emit("paragraphChangeSpecific", {
          ...args,
          updateParagraphUserChangedPids,
          unfixToFixedPids,
          userFixedPids,
        });
      })
    );
  }

  resetControllerInfos() {
    this.undoRedoStacker.reset();
    this.voiceWordController.reset();
    this.searchReplaceController.reset();
    this.paragraphFilterController.reset();
    this.translateController.reset();
    this.textPolishController.reset(); // AI改写重置
    this.agendaController.reset();
    this.todoListController.reset();
    this.unfixToFixedController.reset();
    this.speakerController.reset();
    this.currentSelection = undefined;
    this.livingMode = false;
    this.audioLoading = true;
  }

  appendModelHistory(model?: TranModel) {
    if (!model) return;
    this.historyModel = model;
    this.model.appendHistory(model);
    this.emit("paragraphListChange", { maybeChangedPids: [] });
    this.emit("modelValueChange", {});
  }

  hasModelHistory() {
    return !!this.historyModel;
  }

  checkPidInHistoryModel(pi: PID) {
    if (!this.historyModel) return false;
    return !!this.historyModel.pg.find(p => p.pi === pi);
  }

  setModelValue(tranModel: TranModel | undefined) {
    if (tranModel == null || tranModel.pg == null) {
      this.resetModelValue();
    } else {
      this.model.fromJSON(tranModel);
      this.resetControllerInfos();
      this.emit("paragraphListChange", { maybeChangedPids: [] });
      this.emit("modelValueChange", {});
    }
  }

  setShortcutPositioningButtonBottom(num: number) {
    this.emit("updateShortcutPositioningButtonBottom", { num });
  }

  getModelValue() {
    return this.model.book.toJSON();
  }

  resetModelValue() {
    this.model.book.resetModelValue();
    this.resetControllerInfos();
    this.emit("paragraphListChange", { maybeChangedPids: [] });
    this.emit("modelValueChange", {});
  }

  getPByPid(pid: PID, wid?: WID) {
    return this.model.book.getPByPid(pid, wid);
  }

  getWordsBySces(sces: P["sc"]) {
    return sces.map(sc => this.model.book.getWordBySc(sc));
  }

  getParagraph(pid: PID) {
    return this.model.book.transcription.paragraphs[pid];
  }

  isUserChangedSentence(sid: SID) {
    return this.reader.book.isUserChangedSentence(sid);
  }

  getSentencesBySids(sids: SID[]) {
    return this.reader.book.getSentencesBySids(sids);
  }

  getPidByWid(wid: WID) {
    return this.reader.book.getPidByWid(wid);
  }

  getWordRangeByOffset(pid: PID, offset: number) {
    return this.reader.book.getWordRangeByOffset(pid, offset);
  }

  getPids(seeUseSence = false): PID[] {
    const pids = this.reader.book.getPids();

    if (seeUseSence) {
      return pids.filter(pid => this.paragraphFilterController.isPidVisible(pid));
    }
    return pids;
  }

  getParagraphs() {
    return this.reader.book.getParagraphs();
  }

  getParagraphsByUid(uid: UID) {
    return this.getParagraphs().filter(paragraph => paragraph.uid === uid);
  }

  getParagraphByWid(wid: WID) {
    return this.reader.book.getParagraphByWid(wid);
  }

  getParagraphBySid(sid: SID) {
    return this.reader.book.getParagraphBySid(sid);
  }

  getWords(pid: PID) {
    return this.reader.book.getWords(pid);
  }

  getWordsByWids(wids: WID[]) {
    return this.reader.book.getWordsByWids(wids);
  }

  getWord(wid: WID) {
    return this.reader.book.getWord(wid);
  }

  isAppearedWid(wid: WID) {
    return this.reader.book.isAppearedWid(wid);
  }

  getFirstWordBySid(sid: SID) {
    return this.reader.book.getFirstWordBySid(sid);
  }

  getLatestSid() {
    return this.reader.book.getLatestSid();
  }

  getWordsByOffset(pid: string, startOffset: number, endOffset: number) {
    const words = this.getWords(pid);
    const matchedWords: Word[] = [];
    let offset = 0;

    words.forEach(word => {
      const nextOffset = offset + word.text.length;

      if (startOffset < nextOffset && endOffset > offset) {
        matchedWords.push(word);
      }
      offset = nextOffset;
    });

    return matchedWords;
  }

  getParagraphWordsBySelection(selection: Selection) {
    const paragraphWords = new Map<PID, Word[]>();
    const paragraphRanges = getParagraphRanges(this, selection.range);

    paragraphRanges.forEach((range, pid) => {
      const words = this.getWordsByOffset(pid, range.start.offset, range.end.offset);
      paragraphWords.set(pid, words);
    });
    return paragraphWords;
  }

  getWordsBySelection(selection: Selection) {
    const paragraphWords = this.getParagraphWordsBySelection(selection);
    const words: Word[] = [];
    paragraphWords.forEach(_words => {
      words.push(..._words);
    });
    return words;
  }

  getTextContent(pid: PID) {
    return this.reader.book.getTextContent(pid);
  }

  getTextContentWithoutNfixing(pid: PID) {
    return this.reader.book.getTextContentWithoutNfixing(pid);
  }

  getWordByOffset(pid: PID, offset: number) {
    return this.reader.book.getWordByOffset(pid, offset);
  }

  getAllTextContent() {
    return this.reader.book.getAllTextContent();
  }

  getAllUidsArrayAndMap() {
    return this.reader.book.getAllUidsArrayAndMap();
  }

  getAllUids() {
    return this.reader.book.getAllUids();
  }

  getAllUidsMap() {
    return this.reader.book.getAllUidsMap();
  }

  getTextContentBySelection(): string[] {
    const results: string[] = [];
    const selection = this.getCurrentSelection();
    if (!selection) {
      return results;
    }
    const { start, end } = selection.range;
    const pids = this.getPids();
    const effectPids = start.pid === end.pid ? [start.pid] : getEffectKeys(pids, selection.range);
    effectPids.forEach(pid => {
      const isSelectStart = start.pid === pid;
      const isSelectEnd = end.pid === pid;
      const isSelectMiddle = !isSelectStart && !isSelectEnd;
      const textContent = this.getTextContent(pid);

      if (isSelectMiddle) {
        results.push(textContent);
        return;
      }

      const startOffset = isSelectStart ? start.offset : 0;
      const endOffset = isSelectEnd ? end.offset : textContent.length;

      results.push(textContent.slice(startOffset, endOffset));
    });
    return results;
  }

  getAllTextContentByTag(tag: string) {
    const textContent: string[] = [];
    this.getPids().forEach(pid => {
      const words = this.getWords(pid);
      const text = words.filter(word => word.tag === tag).map(word => word.text);
      if (text) {
        textContent.push(...text);
      }
    });

    return textContent;
  }

  getAllWords(seeUseSence = false): Array<[PID, Word]> {
    const pids = this.getPids(seeUseSence);
    const allWords = pids
      .map<[PID, Word[]]>(pid => [pid, this.getWords(pid)])
      .reduce<Array<[PID, Word]>>((result, [pid, words]) => {
        result.push(...words.map<[PID, Word]>(word => [pid, word]));
        return result;
      }, []);

    return allWords;
  }

  // return [allWords, firstFindPos, matchedWord]
  getMatchedWordsByTime(timestamp: number): [Array<[PID, Word]>, number, Array<[PID, Word]>] {
    const allWord = this.getAllWords();
    let lastFindPos = 0;
    const matchedWords: Array<[PID, Word]> = [];

    allWord.some(([pid, word], index) => {
      if (timestamp > word.endTime) {
        lastFindPos = index + 1;
      } else if (timestamp < word.beginTime) {
        return true;
      } else if (word.beginTime <= timestamp && word.endTime >= timestamp) {
        matchedWords.push([pid, word]);
      }
      return false;
    });
    return [allWord, lastFindPos, matchedWords];
  }

  getFirstWordByTime(timestamp: number) {
    const [allWords, pos, matchedWords] = this.getMatchedWordsByTime(timestamp);
    return matchedWords.length > 0 ? matchedWords[0] : allWords[pos];
  }

  getLastWordByTime(timestamp: number) {
    const [allWords, pos, matchedWords] = this.getMatchedWordsByTime(timestamp);
    return matchedWords.length > 0
      ? matchedWords[matchedWords.length - 1]
      : allWords[Math.max(pos - 1, 0)];
  }

  getVisibleParagraphBySids(sids: SID[]): Paragraph | undefined {
    const visiblePids = this.getPids(true);
    const { length } = sids;

    for (let i = 0; i < length; i++) {
      const sid = sids[i];
      const paragraph = this.getParagraphBySid(sid);
      if (paragraph && visiblePids.includes(paragraph.pid)) {
        return paragraph;
      }
    }
    return undefined;
  }

  getVisibleParagraphBySectionTimes(sTime: number, eTime: number) {
    const paragraphs = this.getParagraphs();
    for (let index = 0; index < paragraphs.length; index++) {
      const paragraph = paragraphs[index];
      const words = this.getWords(paragraph.pid);
      if (words.length) {
        const { beginTime } = words[0];
        const { endTime } = words[words.length - 1];
        if (sTime <= endTime && eTime >= beginTime) {
          return true;
        }
      }
    }
    return false;
  }

  getParagraphByTimestampRange(range: CommonRange) {
    return this.reader.book.getParagraphByTimestampRange(range);
  }
  getParagraphAgendaBystartTimestamp(time: number) {
    return this.reader.book.getParagraphAgendaByStartTimestamp(time);
  }

  getVisibleParagraphByTimestampRange(range: CommonRange) {
    const visiblePids = this.getPids(true);
    return this.getParagraphByTimestampRange(range).filter(paragraph => {
      return visiblePids.includes(paragraph.pid);
    });
  }
  getVisibleAgendaByStartTimestamp(range: number) {
    const visiblePids = this.getPids(true);
    return this.getParagraphAgendaBystartTimestamp(range).filter(paragraph => {
      return visiblePids.includes(paragraph.pid);
    });
  }
  silent() {
    this.setter.nextApplySilent();
    return this;
  }

  preteat() {
    this.setter.nextApplyPreteat();
    return this;
  }

  deleteTextRange(range: Range) {
    if (isEqual(range.start, range.end)) {
      return;
    }
    const effectPids = getEffectKeys(this.getPids(), range);
    effectPids.forEach(pid => {
      if (this.getParagraph(pid).isUnfix) {
        this.unfixChangedWaiting[pid] = true;
      }
    });

    this.setter.book.deleteTextRange(range);
  }

  setUnfixMaxWords(unfixMaxWords: number) {
    this.unfixMaxWords = unfixMaxWords;
  }

  getUnfixMaxWords() {
    return this.unfixMaxWords;
  }

  // 不在包含nfixing中文字的计算
  isGreaterThenMaxCount(pid: PID, startOffset: number, text: string) {
    report("call isGreaterThenMaxCount", { pid, startOffset, text });
    if (this.getLivingMode() === false) {
      return false;
    }

    const paragraph = this.getParagraph(pid);
    if (!paragraph.isUnfix) {
      return false;
    }
    const word = this.getWordByOffset(pid, startOffset);
    if (word?.nFix) {
      return false;
    }

    const textContent = this.getTextContentWithoutNfixing(pid);
    const string = textContent + text;
    const length = getWordsCount(string);
    report("call isGreaterThenMaxCount: word and length", { word, length });
    return length > this.getUnfixMaxWords();
  }

  insertText(pid: PID, startOffset: number, text: string) {
    report("call insertText", { pid, startOffset, text });
    if (this.getParagraph(pid).isUnfix) {
      this.unfixChangedWaiting[pid] = true;
    }
    this.setter.book.insertText(pid, startOffset, text);
  }

  // 不在包含nfixing中文字的计算
  insertTextAndAddParagraph(pid: PID, startOffset: number, text: string) {
    report("call insertTextAndAddParagraph", { pid, startOffset, text });
    if (this.getParagraph(pid).isUnfix) {
      this.unfixChangedWaiting[pid] = true;
    }
    this.setter.book.insertTextAndAddParagraph(pid, startOffset, text);
    this.emitAsync("addFixParagraph", {});

    this.emit("uglyUserManualAddParagraph", {});
  }

  genNewPid(prevPid: PID) {
    const pids = this.getPids();
    const pos = pids.indexOf(prevPid);
    const nextPid: string | undefined = pids[pos + 1];

    const newPid: PID = generatePid(prevPid, nextPid);
    if (newPid !== prevPid && newPid !== nextPid) {
      return newPid;
    }
    return undefined;
  }

  addParagraph(prevPid: PID, startOffset: number) {
    if (this.getParagraph(prevPid).isUnfix) {
      this.unfixChangedWaiting[prevPid] = true;
    }
    const newPid = this.genNewPid(prevPid);
    if (newPid) {
      this.setter.book.addParagraph(prevPid, newPid, startOffset);
    }
  }

  mergePrevParagraph(pid: PID) {
    const prevId = getPreviousPid(this, pid);
    if (!prevId) {
      return;
    }
    if (this.getParagraph(pid).isUnfix) {
      this.unfixChangedWaiting[pid] = true;
    }
    this.setter.book.mergeParagraph(prevId, pid);
  }

  removeParagraph(pid: PID) {
    this.setter.book.removeParagraph(pid);
  }

  clearFixedParagraph() {
    const paragraphs = this.getParagraphs();
    paragraphs.forEach(({ pid, isUnfix }) => {
      if (!isUnfix) {
        this.setter.book.removeParagraph(pid);
      }
    });
  }

  replaceText(
    pid: PID,
    startOffset: number,
    endOffset: number,
    replaceText: string,
    preteatInsertText = false
  ) {
    if (this.getParagraph(pid).isUnfix) {
      this.unfixChangedWaiting[pid] = true;
    }
    this.preteat();
    this.deleteTextRange(
      Range.create({
        start: Point.create(pid, startOffset),
        end: Point.create(pid, endOffset),
      })
    );
    if (preteatInsertText) {
      this.preteat();
    }
    this.insertText(pid, startOffset, replaceText);
  }

  getUserByPid(pid: PID) {
    const p = this.getParagraph(pid);
    return p?.uid || "";
  }

  updateUserByPid(pid: PID, uid: UID) {
    if (this.getParagraph(pid).isUnfix) {
      this.unfixChangedWaiting[pid] = true;
    }
    this.setter.book.updateUserByPid(pid, uid);
  }

  // unfix场景没有修改全部用户功能，暂不增加unfixChangedWaiting逻辑
  updateAllUsers(pids: PID[], uid: UID) {
    this.setter.book.updateAllUsers(pids, uid);
  }

  findAndMark(keyword: string, isSearchTranslate = true) {
    return this.searchReplaceController.findAndMark(keyword, isSearchTranslate);
  }

  findNext() {
    return this.searchReplaceController.next();
  }

  findPrev() {
    return this.searchReplaceController.prev();
  }

  replace(text: string) {
    return this.searchReplaceController.replace(text);
  }

  replaceAll(text: string) {
    return this.searchReplaceController.replaceAll(text);
  }

  getFoundSelectionsByPid(pid: PID) {
    return this.searchReplaceController.getFoundSelectionsByPid(pid);
  }

  getActiveFoundSelection() {
    return this.searchReplaceController.getActiveFoundSelection();
  }

  resetFind() {
    return this.searchReplaceController.resetFind();
  }

  setPlayingVoiceWord(pid: PID, word: Word | undefined, forceDisplay = false) {
    this.voiceWordController.setPlayingVoiceWord(pid, word, forceDisplay);

    if (this.agendaController.getEnableActiveAgenda() && word) {
      this.setActiveAgendaByTime(word.beginTime);
    }

    if (this.todoListController.getEnableActivetodoList() && word) {
      this.setActiveTodoListByTime(word.beginTime);
    }
  }

  setPlayingVoiceWordByTime(timestamp: number, forceDisplay = false) {
    this.voiceWordController.setPlayingVoiceWordByTime(timestamp, forceDisplay);
    if (this.agendaController.getEnableActiveAgenda()) {
      this.agendaController.setActiveAgendaByTime(timestamp);
    }

    if (this.todoListController.getEnableActivetodoList()) {
      this.todoListController.setActivetodoListByTime(timestamp);
    }
  }

  getPlayingPidAndWord() {
    return this.voiceWordController.getPlayingPidAndWord();
  }

  getPlayingWord(pid: PID) {
    return this.voiceWordController.getPlayingWord(pid);
  }

  clearPlayingVoiceWord() {
    this.voiceWordController.clearPlayingVoiceWord();
  }

  getNextSentencesByTime(timestamp: number) {
    return this.voiceWordController.getNextSentencesByTime(timestamp);
  }

  getPrevSentencesByTime(timestamp: number) {
    return this.voiceWordController.getPrevSentencesByTime(timestamp);
  }

  /**
   * Agenda
   */
  setActiveAgendaId(agendaId: string) {
    this.agendaController.setActiveAgendaById(agendaId);
  }

  /**
   * Agenda
   */
  setActiveAgendaById(agendaId: string) {
    this.agendaController.setActiveAgendaById(agendaId);
    const activeAgenda = this.getActiveAgenda();
    if (activeAgenda) {
      const { beginTime } = activeAgenda;
      this.voiceWordController.setPlayingVoiceWordByTime(beginTime, false, false, true);
    }
  }

  /**
   * Agenda-meeting
   */
  setMeetingActiveAgendaById(agendaId?: string) {
    this.agendaController.setMeetingActiveAgendaById(agendaId);
  }

  clearActiveAgenda() {
    this.agendaController.clearActiveAgenda();
  }

  setActiveAgendaByTime(timestamp: number) {
    this.agendaController.setActiveAgendaByTime(timestamp);
  }

  getActiveAgenda() {
    return this.agendaController.getActiveAgenda();
  }

  setReviewHeight(reviewHeight: number) {
    this.reviewHeight = reviewHeight;
  }

  getReviewHeight() {
    return this.reviewHeight;
  }

  setAgendasFromJSON(cps: AgendaCheckpoint[]) {
    this.agendaController.fromJSON(cps);
  }

  getActiveAgendaHighlightSids() {
    return this.agendaController.getActiveAgendaHighlightSids();
  }

  getAgendas() {
    return this.agendaController.getAgendas();
  }

  setAgendas(agendas: Agenda[]) {
    this.agendaController.setAgendas(agendas);
  }

  detectedFirstShowUpAgenda(agenda: Agenda) {
    this.emit("detectedFirstShowUpAgenda", {
      agenda,
    });
  }

  getEnableActiveAgenda() {
    return this.agendaController.getEnableActiveAgenda();
  }

  /**
   * todoList
   */
  setActiveTodoListId(todoListId: string) {
    this.todoListController.setActivetodoListById(todoListId);
  }

  /**
   * todoList
   */
  setActiveTodoListById(todoListId: string) {
    this.todoListController.setActivetodoListById(todoListId);
    const activeTodoList = this.getActiveTodoList();
    if (activeTodoList) {
      this.voiceWordController.setPlayingVoiceWordByTime(
        activeTodoList.beginTime,
        false,
        false,
        true
      );
    }
  }

  clearActiveTodoList() {
    this.todoListController.clearActivetodoList();
  }

  setActiveTodoListByTime(timestamp: number) {
    this.todoListController.setActivetodoListByTime(timestamp);
  }

  getActiveTodoList() {
    return this.todoListController.getActivetodoList();
  }

  setTodoListsFromJSON(cps: TodoListCheckpoint[]) {
    this.todoListController.fromJSON(cps);
  }

  getActiveTodoListHighlightSids() {
    return this.todoListController.getActivetodoListHighlightSids();
  }

  getMeetingActiveAgendaPid() {
    return this.agendaController.getMeetingActiveAgendaPid();
  }

  getTodoLists() {
    return this.todoListController.gettodoLists();
  }

  setTodoLists(todoLists: TodoList[]) {
    this.todoListController.settodoLists(todoLists);
  }

  detectedFirstShowUpTodoList(todoList: TodoList) {
    this.emit("activetodoListChange", {
      todoList,
    });
  }

  getEnableActiveTodoList() {
    return this.agendaController.getEnableActiveAgenda();
  }

  /**
   * @deprecated
   */
  prepareSetPlayingWord() {
    console.warn("请替换prepareSetPlayingWord为startPlayingVoiceWord");
    this.startPlayingVoiceWord();
  }

  startPlayingVoiceWord() {
    this.voiceWordController.startPlayingVoiceWord();
  }

  endPlayingVoiceWord() {
    this.voiceWordController.endPlayingVoiceWord();
  }

  getIsEnablePlayingVoice() {
    return this.voiceWordController.getIsEnablePlayingVoice();
  }

  private readonly genSelectWordHandler =
    (callback: (words: Word[] | undefined) => void) => (args: SelectionParamsType) => {
      const { selection } = args;
      if (selection) {
        let startOffset = 0;
        let endOffset = 0;
        let pid = "";
        let readonly = false;
        const words = [];

        if ("range" in selection) {
          const { range } = selection;
          const { start, end } = range;
          startOffset = start.offset;
          endOffset = end.offset;
          pid = start.pid;
          words.push(...this.getWords(pid), ...this.getWords(end.pid));
        } else if ("pid" in selection) {
          startOffset = selection.startOffset;
          endOffset = selection.endOffset;
          pid = selection.pid;
          readonly = !!selection.readonly;
          words.push(...this.getWords(pid));
        }

        // translate
        if (readonly) return;

        const matchWords: Word[] = [];
        let offset = 0;
        words.some((word, index) => {
          const nextOffset = offset + word.text.length;
          if (startOffset === endOffset) {
            if (startOffset > offset && startOffset <= nextOffset) {
              matchWords.push(word);
              return true;
            } else if (startOffset === 0 && index === 0) {
              matchWords.push(word);
              return true;
            }
          } else if (
            (startOffset <= offset && endOffset > offset) ||
            (endOffset >= nextOffset && startOffset < nextOffset) ||
            (startOffset >= offset && endOffset <= nextOffset)
          ) {
            matchWords.push(word);
          }
          if (endOffset < offset) {
            return true;
          }
          offset = nextOffset;
          return false;
        });

        if (matchWords[0]) {
          // this.setPlayingVoiceWord(pid, matchWords[0]);
        }

        callback(matchWords);
      } else {
        callback(undefined);
      }
    };

  listenSelectWordhandler: ((args: SelectionParamsType) => void) | undefined = undefined;
  listenSearchReplaceChagnehandler: ((args: Eventes["searchReplaceChange"]) => void) | undefined =
    undefined;
  listenNeedSeekToWord: ((args: Eventes["needSeekToWord"]) => void) | undefined = undefined;
  startListenSelectWordCallback: ((word: Word[] | undefined) => void) | undefined;

  startListenSelectWord = (callback: (word: Word[] | undefined) => void) => {
    if (this.listenSelectWordhandler != null) {
      console.error(
        "You are listening `select word` events. " +
          "please check your code. " +
          "dont listen this events twice."
      );
      return;
    }

    this.startListenSelectWordCallback = callback;
    this.listenSelectWordhandler = this.genSelectWordHandler(callback);
    const { listenSelectWordhandler } = this;
    this.listenSearchReplaceChagnehandler = (event: Eventes["searchReplaceChange"]) => {
      listenSelectWordhandler({
        selection: event.selections[event.activeIndex],
      });
    };

    this.listenNeedSeekToWord = (event: Eventes["needSeekToWord"]) => callback([event.word]);
    this.on("mouseUpSelectionChange", this.listenSelectWordhandler);
    this.on("searchReplaceChange", this.listenSearchReplaceChagnehandler);
    this.on("needSeekToWord", this.listenNeedSeekToWord);

    return this.endListenSelectWord;
  };

  endListenSelectWord = () => {
    this.off("mouseUpSelectionChange", this.listenSelectWordhandler);
    this.off("searchReplaceChange", this.listenSearchReplaceChagnehandler);
    this.off("needSeekToWord", this.listenNeedSeekToWord);
    this.listenSelectWordhandler = undefined;
    this.listenSearchReplaceChagnehandler = undefined;
    this.listenNeedSeekToWord = undefined;
    this.startListenSelectWordCallback = undefined;
  };

  seekToWord(word: Word) {
    this.emit("needSeekToWord", {
      word,
    });
  }

  readonlyClickWord(words: Word[]) {
    if (this.startListenSelectWordCallback) {
      this.startListenSelectWordCallback(words);
    }
  }

  extraSelectContents(type: string) {
    const contents = this.getTextContentBySelection();
    if (contents.length === 0) {
      return;
    }

    const pid = this.currentSelection?.range?.start?.pid || "";
    const wid = this.currentSelection
      ? this.getWordsBySelection(this.currentSelection)?.[0]?.wid
      : "";

    const selection = this.getCurrentSelection();
    if (!selection) return;
    const { start, end } = selection.range;
    const pids = this.getPids();
    const effectPids = start.pid === end.pid ? [start.pid] : getEffectKeys(pids, selection.range);
    const paragraphContents = effectPids.map((id, index) => ({
      pid: id,
      wid: (index === 0 ? wid : this.getWords(id)[0]?.wid) || "",
      text: contents[index],
    }));

    this.emit("extraContents", {
      contents,
      type,
      pid,
      wid,
      paragraphContents,
    });
  }

  extraContent(contents: string[], type: string, pid: PID, sid: SID) {
    this.emit("extraContents", {
      contents,
      type,
      pid,
      sid,
    });
  }

  markSelectContents(selection: Selection, tag: string | undefined) {
    const paragraphWords = this.getParagraphWordsBySelection(selection);
    const attrs = {
      tag,
    };

    paragraphWords.forEach((_words, pid) => {
      const paragraph = this.getParagraph(pid);
      if (paragraph.isUnfix) {
        this.unfixChangedWaiting[pid] = true;
      }
    });

    this.setter.book.updateParagraphWords(paragraphWords, attrs);
    this.emit("markWordsChanged", {
      markedContinuousWords: this.getMarkedContinuousWords(),
    });
    this.focusEditor();
  }

  getSelectionMarkTag(): string | undefined {
    const selection = this.getCurrentSelection();
    if (!selection) {
      return undefined;
    }
    const paragraphWords = this.getParagraphWordsBySelection(selection);
    const words: Word[] = [];
    let result: string | undefined;

    paragraphWords.forEach(_words => {
      words.push(..._words);
    });

    words.some(word => {
      if (result == null) {
        result = word.tag;
      } else if (result !== word.tag) {
        result = undefined;
        return true;
      }
      return false;
    });

    return result;
  }

  getMarkedContinuousWords(tag?: string): Word[][] {
    const continuousWords: Word[][] = [];
    const allWords = this.getAllWords();

    let foundTag: string | undefined;

    const { length } = allWords;
    for (let i = 0; i < length; i++) {
      const [, word] = allWords[i];
      if (!foundTag || word.tag !== foundTag) {
        if (word.tag && (tag == null || word.tag === tag)) {
          foundTag = word.tag;
          continuousWords.push([]);
        } else {
          foundTag = undefined;
        }
      }
      if (foundTag && word.tag === foundTag) {
        continuousWords[continuousWords.length - 1].push(word);
      }
    }

    return continuousWords;
  }

  needStartPlayVoiceWithSelection(selection: Selection) {
    const paragraphWords = this.getParagraphWordsBySelection(selection);
    const words: Word[] = [];

    paragraphWords.forEach(_words => {
      words.push(..._words);
    });
    this.emit("needStartPlayVoice", {
      words,
    });
  }

  setFilterOption(filterOption: FilterOption) {
    this.paragraphFilterController.setFilterOption(filterOption);
    this.searchReplaceController.reFindAndMark();
    this.emit("paragraphFilterChange", {});
  }

  getFilterOption() {
    return this.paragraphFilterController.getFilterOption();
  }

  setTranslateMode(mode: TranslateMode) {
    this.translateController.setTranslateMode(mode);
    this.searchReplaceController.reFindAndMark();
  }

  getTranslateMode(pid?: PID) {
    return this.translateController.getTranslateMode(pid);
  }

  setFileModel(fileModel: string) {
    this.fileModel = fileModel;
  }
  getFileModel() {
    return this.fileModel;
  }

  setTranslate(pid: PID, translate: SC[]) {
    const isSuccessful = this.translateController.setTranslate(pid, translate);
    if (isSuccessful) {
      this.searchReplaceController.reFindAndMark();
    }
  }

  setTranslateLanguage(language: TranslateLanguage) {
    this.language = language;
  }

  getTranslateLanguage() {
    return this.language;
  }

  setShowHeaderTime(value: boolean) {
    this.showHeaderTime = value;
  }
  getShowHeaderTime() {
    return this.showHeaderTime;
  }

  setTranslateByWords(pid: PID, translate: Word[]) {
    const isSuccessful = this.translateController.setTranslateByWords(pid, translate);
    if (isSuccessful) {
      this.searchReplaceController.reFindAndMark();
    }
  }

  setTranslating(pid: PID, isTranslating: boolean) {
    this.translateController.setTranslating(pid, isTranslating);
  }

  getTranslate(pid: PID) {
    return this.translateController.getTranslate(pid);
  }

  setUnfixTranslate(
    pid: PID | undefined,
    unfixContentTranslate: string,
    fixContentTranslate: SC[]
  ) {
    this.translateController.setUnfixTranslate(pid, unfixContentTranslate, fixContentTranslate);
    this.searchReplaceController.reFindAndMark();
  }

  setUnfixTranslateByWords(
    pid: PID | undefined,
    unfixContentTranslate: string,
    fixContentTranslate: Word[]
  ) {
    this.translateController.setUnfixTranslateByWords(
      pid,
      unfixContentTranslate,
      fixContentTranslate
    );
    this.searchReplaceController.reFindAndMark();
  }

  getUnfixTranslate() {
    return this.translateController.getUnfixTranslate();
  }

  /** ************* START 【AI 原文改写】 START: ************* */

  /**
   * 设置AI改写模式
   * @param newMode 新改写模式
   */
  setTextPolishMode(mode: TextPolishMode) {
    this.textPolishController.setTextPolishMode(mode);
    this.searchReplaceController.reFindAndMark();
  }
  /** 获取当前AI改写模式 */
  getTextPolishMode() {
    return this.textPolishController.getTextPolishMode();
  }
  /**
   * 设置AI改写的段落信息
   * @param pid  段落id
   * @param textPolish 段落信息
   */
  setTextPolish(pid: PID, textPolish: TextPolishResult) {
    const isSuccessful = this.textPolishController.setTextPolish(pid, textPolish);
    if (isSuccessful) {
      this.searchReplaceController.reFindAndMark();
    }
  }
  /**
   * 根据段落Id获取AI改写段落信息
   * @param pid 段落id
   * @returns
   */
  getTextPolish(pid: PID) {
    return this.textPolishController.getTextPolish(pid);
  }
  /** ************* END 【AI 原文改写】 END ************* */

  emitParagraphNeedTranslate(pid: PID) {
    this.emit("paragraphNeedTranslate", { pid });
  }

  injectSpeakersGetter(speakersGetter: SpeakerGetter) {
    this.speakerController.injectSpeakersGetter(speakersGetter);
  }

  injectSpeakersSetter(speakersSetter: SpeakerSetter) {
    this.speakerController.injectSpeakersSetter(speakersSetter);
  }

  // 常用发言人
  injectCommonUseSpeakersGetter(speakersGetter: CommonUseSpeakerGetter) {
    this.speakerController.injectCommonUseSpeakersGetter(speakersGetter);
  }

  // 常用发言人
  injectCommonUseSpeakersSetter(speakersSetter: CommonUseSpeakerSetter) {
    this.speakerController.injectCommonUseSpeakersSetter(speakersSetter);
  }

  injectDefaultSpeakerNameGetter(defaultSpeakerNameGetter: DefaultSpeakerNameGetter) {
    this.speakerController.injectDefaultSpeakerNameGetter(defaultSpeakerNameGetter);
  }

  injectDefaultSpeakerAvatarGetter(defaultSpeakerAvatarGetter: DefaultSpeakerAvatarGetter) {
    this.speakerController.injectDefaultSpeakerAvatarGetter(defaultSpeakerAvatarGetter);
  }

  injectRenderAvatarGetter(renderAvatarGetter: RenderAvatarGetter) {
    this.speakerController.injectRenderAvatarGetter(renderAvatarGetter);
  }

  getSpeakers() {
    return this.speakerController.getSpeakers();
  }

  setSpeakers(speakers: Speaker[]) {
    this.speakerController.setSpeakers(speakers);
  }

  setMaxSpeaker(maxSpeaker: number) {
    this.speakerController.setMaxSpeaker(maxSpeaker);
  }

  getMaxSpeaker() {
    return this.speakerController.getMaxSpeaker();
  }

  getCommonUseSpeakers() {
    return this.speakerController.getCommonUseSpeakers();
  }

  setCommonUseSpeakers(speakers: CommonUseSpeaker[]) {
    this.speakerController.setCommonUseSpeakers(speakers);
  }

  renderAvatar(uid: UID, size?: number) {
    return this.speakerController.renderAvatar(uid, size);
  }

  // todo----未必会使用，可能只有set就够了
  updateCommonUseSpeakersList(speakers: CommonUseSpeaker[]) {
    this.speakerController.updateCommonUseSpeakersList(speakers);
  }

  updateOneByAlibabaEmployeeOnEmptyUnfix(alibabaEmployee: AlibabaEmployee) {
    this.speakerController.updateOneByAlibabaEmployeeOnEmptyUnfix(alibabaEmployee);
  }

  updateOneByHistorySpeakerOnEmptyUnfix(speaker: Speaker) {
    this.speakerController.updateOneByHistorySpeakerOnEmptyUnfix(speaker);
  }
  updateOneByInputTextOnEmptyUnfix(oldInputText: string, inputText: string) {
    this.speakerController.updateOneByInputTextOnEmptyUnfix(oldInputText, inputText);
  }

  updateOneByAlibabaEmployee(pid: PID, alibabaEmployee: AlibabaEmployee) {
    this.speakerController.updateOneByAlibabaEmployee(pid, alibabaEmployee);
  }

  updateOneByHistorySpeaker(pid: PID, speaker: Speaker) {
    this.speakerController.updateOneByHistorySpeaker(pid, speaker);
  }

  updateOneByInputText(pid: PID, oldInputText: string, inputText: string) {
    this.speakerController.updateOneByInputText(pid, oldInputText, inputText);
  }

  updateManyByAlibabaEmployee(currentPid: PID, alibabaEmployee: AlibabaEmployee) {
    this.speakerController.updateManyByAlibabaEmployee(currentPid, alibabaEmployee);
  }

  updateManyByHistorySpeaker(currentPid: PID, speaker: Speaker) {
    this.speakerController.updateManyByHistorySpeaker(currentPid, speaker);
  }

  updateManyByInputText(currentPid: PID, oldInputText: string, inputText: string) {
    this.speakerController.updateManyByInputText(currentPid, oldInputText, inputText);
  }

  renderSpeakerName(uid?: UID) {
    return this.speakerController.renderSpeakerName(uid);
  }

  renderSpeakerHighlight(uid?: UID) {
    return this.speakerController.renderSpeakerHighlight(uid);
  }

  getSpeaker(uid: UID) {
    return this.speakerController.getSpeaker(uid);
  }

  getLastUnfixPid() {
    return this.unfixToFixedController.getLastUnfixPid();
  }

  getUnfixToFixedPid() {
    return this.unfixToFixedController.getUnfixToFixedPid();
  }

  getUnfixToFixedKeepFocusPid() {
    return this.unfixToFixedController.getUnfixToFixedKeepFocusPid();
  }

  isFirstUnfixPid(pid: PID | undefined) {
    return this.unfixToFixedController.isFirstUnfixPid(pid);
  }

  emitSaveDocument() {
    this.emit("saveDocument", {});
  }

  getParagraphCaptionsMap() {
    return this.captionCmdController.getParagraphCaptionsMap();
  }

  getCaptionArray() {
    return this.captionCmdController.getCaptionArray();
  }

  getCaptionByTimestamp(timestamp: number) {
    return this.captionCmdController.getCaptionByTimestamp(timestamp);
  }

  getAllExtractContent() {
    return this.extractController.getAllExtractContent();
  }

  getExtraContentByTag(tag?: string) {
    return this.extractController.getExtraContentByTag(tag);
  }
  // 获取全部的翻译数据
  getAllExtractContentByTranslate() {
    return this.extractController.getAllExtractContentByTranslate();
  }
  // 获取全部的改写数据
  getAllExtractContentByTextPolish() {
    return this.extractController.getAllExtractContentByTextPolish();
  }

  undo() {
    this.undoRedoStacker.undo();
  }

  redo() {
    this.undoRedoStacker.redo();
  }

  canUndo() {
    return this.undoRedoStacker.canUndo();
  }

  canRedo() {
    return this.undoRedoStacker.canRedo();
  }

  recordTimeout: ReturnType<typeof setTimeout> | undefined;

  isHasContent() {
    return this.getAllTextContent().join("") !== "";
  }

  startRecordResultCheck() {
    if (this.isHasContent()) {
      return;
    }

    this.recordTimeout = setTimeout(() => {
      if (!this.isHasContent()) {
        this.emit("detectEmptyContent", {});
        this.stopRecordResultCheck();
      }
    }, 30000);
  }

  stopRecordResultCheck() {
    clearTimeout(this.recordTimeout);
    this.recordTimeout = undefined;
  }

  clearNoRecordTip() {
    // 外部可控制关闭NoRecordTip
    this.emit("closeNoRecordTip", {});
  }

  startSelectionAt(point: Point) {
    this.selectionController.startSelectionAt(point);
  }

  pendingSelectionAt(point: Point) {
    this.selectionController.pendingSelectionAt(point);
  }

  endSelectionAt(point?: Point) {
    this.selectionController.endSelectionAt(point);
  }

  isSelectionPending(isUnfix: boolean) {
    return this.selectionController.isSelectionPending(isUnfix);
  }

  registerTextEditor(pid: PID, handler: GetEditorHandler) {
    this.selectionController.registerTextEditor(pid, handler);
  }

  unregisterTextEditor(pid: PID) {
    this.selectionController.unregisterTextEditor(pid);
  }

  getSelectionStyles(pid: PID) {
    return this.selectionController.getSelectionStyles(pid);
  }

  mergeSelectionAt(selection: Selection, point: Point) {
    this.selectionController.mergeSelectionAt(selection, point);
  }

  clearPendingPoint() {
    this.selectionController.clearPendingPoint();
  }

  isCaretPending() {
    return this.textBoxController.caretPending;
  }

  handleTextboxInput() {
    this.textBoxController.handleTextboxInput();
  }

  finishRender(pid: PID) {
    this.selectionController.finishRender(pid);
  }

  getParagraphDOM(pid: PID) {
    return this.selectionController.getParagraphDOM(pid);
  }

  calcSelectionStyles() {
    this.selectionController.calcSelectionStyles();
  }

  calcCaretStyles(isTextBoxInput = true) {
    isTextBoxInput && this.handleTextboxInput();
    return this.selectionController.calcCaretStyles();
  }

  getMergedSelectionRect() {
    return this.selectionController.getMergedSelectionRect();
  }

  focusEditor() {
    this.textBoxController.focusEditor();
  }

  setCustomMeetingController(customMeetingController: CustomMeetingController) {
    this.customMeetingController = customMeetingController;
  }
}
