import { AbstractCmdController } from "./abstractCmdController";
import type { Controller } from "./index";
import { PID, UID } from "../persist";
import {
  getSpeaker,
  firstSpeaker,
  getSpeakerName,
  getSpeakerHighlight,
  getDisplayAlibabaEmployeeName,
  generateSpeakerUid,
  findSpeakerByUid,
  findSpeakerByBizUid,
  findSpeakerPosByBizUid,
  findNotAlibabaSpeakerByName,
  getBeforeChangeSpeakers,
} from "../utils";

const MAX_SPEAKER = 50;

export interface AlibabaEmployee {
  avatarUrl?: string;
  bizUid: string;
  departName: string;
  name: string;
  nickName: string;
  userId?: number;
}

export interface Speaker {
  ui?: UID; // 更新前的 UI
  uid: UID;
  name: string;
  avatarUrl?: string;
  bizUid?: string;
  // 新增字段
  displayName?: string;
  isHighlight?: boolean;
}

export interface CommonUseSpeaker {
  uid: UID;
  name: string;
  avatarUrl?: string;
  bizUid?: string;
}

export interface AddUseSpeakerResult {
  code: string;
  message?: string;
}

export type SpeakerSetter = (speakers: Speaker[]) => void;

export type SpeakerGetter = () => Speaker[];

export type CommonUseSpeakerSetter = (speakers: CommonUseSpeaker[]) => void;

export type CommonUseSpeakerGetter = () => CommonUseSpeaker[];

export type DefaultSpeakerNameGetter = (speaker?: Speaker) => string;

export type DefaultSpeakerAvatarGetter = (speaker?: Speaker) => string;

export type RenderAvatarGetter = (uid: UID, size?: number) => any;

export class SpeakerController extends AbstractCmdController {
  private speakersGetter: SpeakerGetter | undefined;

  private speakersSetter: SpeakerSetter | undefined;

  private defaultSpeakerNameGetter: DefaultSpeakerNameGetter = () => "发言人";

  private defaultSpeakerAvatarGetter: DefaultSpeakerAvatarGetter = () => "";

  private tempRemovedSpeakers: Record<UID, Speaker> = {};

  private commonUseSpeakersGetter: CommonUseSpeakerGetter | undefined;

  private commonUseSpeakersSetter: CommonUseSpeakerSetter | undefined;

  private renderAvatarGetter: RenderAvatarGetter | undefined;

  private maxSpeaker = MAX_SPEAKER;

  private historySpeakerMap: Record<UID, Speaker> = {};

  constructor(controller: Controller) {
    super(controller);
    this.listenEvents();
  }

  injectSpeakersGetter(speakersGetter: SpeakerGetter) {
    this.speakersGetter = speakersGetter;
    const speakers = speakersGetter();
    speakers.forEach(speaker => {
      if (!this.historySpeakerMap[speaker.uid]) {
        this.historySpeakerMap[speaker.uid] = speaker;
      }
    });
  }

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

  injectCommonUseSpeakersGetter(commonUseSpeakersGetter: CommonUseSpeakerGetter) {
    this.commonUseSpeakersGetter = commonUseSpeakersGetter;
  }

  injectCommonUseSpeakersSetter(commonUseSpeakersSetter: CommonUseSpeakerSetter) {
    this.commonUseSpeakersSetter = commonUseSpeakersSetter;
  }

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

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

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

  listenEvents() {
    this.controller.on("emptyUnfixParagraphShowUp", () => {
      const speakers = this.getSpeakers() || [];
      const found = speakers.find(speaker => speaker.uid === firstSpeaker.uid);
      if (!found) {
        this.setSpeakers(speakers.concat(firstSpeaker));
        this.controller.setEmptyUnfixTempUid(firstSpeaker.uid);
      }
    });

    this.controller.on("paragraphChange", ({ pid, oldParagraph }) => {
      const paragraph = this.controller.getParagraph(pid);
      if (paragraph.isUnfix || !paragraph.uid) {
        return;
      }
      const speakers = this.getSpeakers() || [];
      const found = speakers.find(speaker => speaker.uid === paragraph.uid);
      if (!found) {
        const addedSpeaker = this.historySpeakerMap[paragraph.uid] || {
          uid: paragraph.uid,
          name: getSpeakerName(undefined, () => ""),
        };
        // 添加修改前的 UI
        if (oldParagraph && oldParagraph.uid && oldParagraph.uid !== paragraph.uid) {
          addedSpeaker.ui = oldParagraph.uid;
        }

        this.setSpeakers(
          getBeforeChangeSpeakers(this.controller, speakers, []).concat(addedSpeaker)
        );
      }
    });

    this.controller.on(
      "bookParagraphChangeSpecific",
      ({ removeParagraphPids, removeParagraphs, addParagraphPids }) => {
        const speakers = this.getSpeakers();
        if (removeParagraphPids.length > 0) {
          Object.keys(removeParagraphs || {}).forEach(pid => {
            const paragraph = removeParagraphs![pid];
            const { uid } = paragraph;
            if (uid === undefined) return;
            const speaker = findSpeakerByUid(speakers, uid);
            if (speaker) {
              this.tempRemovedSpeakers[uid] = speaker;
            }
          });
          const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, []);
          this.setSpeakers(newSpeakers);
        } else if (addParagraphPids.length > 0) {
          addParagraphPids.forEach(pid => {
            const paragraph = this.controller.getParagraph(pid);
            const { uid } = paragraph;
            if (uid === undefined) return;

            const speaker = findSpeakerByUid(speakers, uid);
            if (this.tempRemovedSpeakers[uid]) {
              if (!speaker) {
                const newSpeakers = speakers.concat(this.tempRemovedSpeakers[uid]);
                this.setSpeakers(newSpeakers);
                this.controller.emit("speakerNameChange", { uid });
              }
              delete this.tempRemovedSpeakers[uid];
            }
          });
        }
      }
    );
  }

  getCommonUseSpeakers = () => {
    if (!this.commonUseSpeakersGetter) {
      throw new Error(
        "You need implement `commonUseSpeakersGetter`, " +
          "please use `controller.injectCommonUseSpeakersGetter` to implement it."
      );
    }

    return this.commonUseSpeakersGetter() || [];
  };

  setCommonUseSpeakers = (speakers: CommonUseSpeaker[]) => {
    if (!this.commonUseSpeakersSetter) {
      throw new Error(
        "You need implement `commonUseSpeakersSetter`, " +
          "please use `controller.injectCommonUseSpeakersSetter` to implement it."
      );
    }

    return this.commonUseSpeakersSetter(speakers);
  };

  // 常用发言人列表更新
  updateCommonUseSpeakersList(speakers: CommonUseSpeaker[]) {
    // const speakers = this.getCommonUseSpeakers();
    this.setCommonUseSpeakers(speakers);
  }

  setMaxSpeaker(maxSpeaker = MAX_SPEAKER) {
    this.maxSpeaker = maxSpeaker;
  }

  getMaxSpeaker() {
    return Math.max(this.getSpeakers().length, this.maxSpeaker);
  }

  isVaildSpeakers(speakers: Speaker[]) {
    if (speakers.length > this.getMaxSpeaker()) {
      this.controller.emit("detectSpeakerFull", {});
      return false;
    }
    return true;
  }

  appendAutoSpeaker(speaker: Speaker) {
    // 更新发言人列表
    const existSpeakers = this.getSpeakers();
    if (!existSpeakers.find(s => s.uid === speaker.uid || s.ui === speaker.uid)) {
      this.setSpeakers([...existSpeakers, speaker]);
    }
    // 取消正在发言人
    // else {
    //   this.controller.changeSpeakingSpeaker(speaker.uid); // 更新正在发言人
    // }
  }

  getSpeakers = () => {
    if (!this.speakersGetter) {
      throw new Error(
        "You need implement `speakersGetter`, " +
          "please use `controller.injectSpeakersGetter` to implement it."
      );
    }

    return this.speakersGetter() || [];
  };

  setSpeakers = (speakers: Speaker[]) => {
    if (!this.speakersSetter) {
      throw new Error(
        "You need implement `speakersSetter`, " +
          "please use `controller.injectSpeakersSetter` to implement it."
      );
    }

    [...speakers, ...this.getSpeakers()].forEach(speaker => {
      if (!this.historySpeakerMap[speaker.uid]) {
        this.historySpeakerMap[speaker.uid] = speaker;
      }
    });

    return this.speakersSetter(speakers);
  };

  renderAvatar = (uid: UID, size?: number) => {
    if (!this.renderAvatarGetter) {
      throw new Error(
        "You need implement `renderAvatarGetter`, " +
          "please use `controller.injectRenderAvatarGetter` to implement it."
      );
    }
    return this.renderAvatarGetter(uid, size);
  };

  getDefaultSpeakerName = (speaker?: Speaker) => {
    return this.defaultSpeakerNameGetter(speaker);
  };

  getDefaultSpeakerAvatar = (speaker?: Speaker) => {
    return this.defaultSpeakerAvatarGetter(speaker);
  };

  updateOneByAlibabaEmployeeOnEmptyUnfix(alibabaEmployee: AlibabaEmployee) {
    const speakers = this.getSpeakers();
    const speaker = findSpeakerByBizUid(speakers, alibabaEmployee.bizUid);
    const tempUid = this.controller.getEmptyUnfixTempUid();

    if (speaker) {
      if (tempUid !== speaker.uid) {
        this.controller.setEmptyUnfixTempUid(speaker.uid);
        const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, [], speaker.uid);

        if (this.isVaildSpeakers(newSpeakers)) {
          this.setSpeakers(newSpeakers);
          this.controller.setEmptyUnfixTempUid(speaker.uid);
        }
      }
    } else {
      const uid = generateSpeakerUid(speakers);
      const addedSpeaker: Speaker = {
        uid,
        name: getDisplayAlibabaEmployeeName(alibabaEmployee),
        avatarUrl: alibabaEmployee.avatarUrl,
        bizUid: alibabaEmployee.bizUid,
      };
      const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, [], uid).concat(
        addedSpeaker
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.setEmptyUnfixTempUid(uid);
      }
    }
  }

  updateOneByHistorySpeakerOnEmptyUnfix(speaker: Speaker) {
    const tempUid = this.controller.getEmptyUnfixTempUid();
    const speakers = this.getSpeakers();
    if (tempUid !== speaker.uid) {
      this.controller.setEmptyUnfixTempUid(speaker.uid);
      const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, [], speaker.uid);
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
      }
    }
  }

  updateOneByInputTextOnEmptyUnfix(oldInputText: string, inputText: string) {
    if (oldInputText === inputText || !inputText) {
      return;
    }
    const tempUid = this.controller.getEmptyUnfixTempUid();
    const speakers = this.getSpeakers();
    const speaker = tempUid ? findSpeakerByUid(speakers, tempUid) : undefined;
    const sameNameSpeaker = findNotAlibabaSpeakerByName(
      speakers,
      inputText,
      this.getDefaultSpeakerName
    );

    if (sameNameSpeaker && sameNameSpeaker.uid !== tempUid) {
      const newSpeakers = getBeforeChangeSpeakers(
        this.controller,
        speakers,
        [],
        sameNameSpeaker.uid
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.setEmptyUnfixTempUid(sameNameSpeaker.uid);
      }
    } else if (!speaker || speaker.name !== inputText) {
      const uid = generateSpeakerUid(speakers);
      const addedSpeaker: Speaker = {
        uid,
        name: inputText,
      };
      const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, [], uid).concat(
        addedSpeaker
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.controller.setEmptyUnfixTempUid(uid);
        this.setSpeakers(newSpeakers);
      }
    }
  }

  updateOneByAlibabaEmployee(pid: PID, alibabaEmployee: AlibabaEmployee) {
    const paragraph = this.controller.getParagraph(pid);
    const speakers = this.getSpeakers();
    const speaker = findSpeakerByBizUid(speakers, alibabaEmployee.bizUid);

    if (speaker) {
      if (paragraph.uid !== speaker.uid) {
        const newSpeakers = getBeforeChangeSpeakers(
          this.controller,
          speakers,
          [paragraph.pid],
          speaker.uid
        );
        if (this.isVaildSpeakers(newSpeakers)) {
          this.setSpeakers(newSpeakers);
          this.controller.updateUserByPid(pid, speaker.uid);
          this.controller.emit("speakerNameChange", {
            uid: speaker.uid,
          });
        }
      }
    } else {
      const uid = generateSpeakerUid(speakers);
      const addedSpeaker: Speaker = {
        uid,
        name: getDisplayAlibabaEmployeeName(alibabaEmployee),
        avatarUrl: alibabaEmployee.avatarUrl,
        bizUid: alibabaEmployee.bizUid,
      };
      const newSpeakers = getBeforeChangeSpeakers(
        this.controller,
        speakers,
        [paragraph.pid],
        uid
      ).concat(addedSpeaker);
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateUserByPid(pid, uid);
      }
    }
  }

  updateOneByHistorySpeaker(pid: PID, speaker: Speaker) {
    const paragraph = this.controller.getParagraph(pid);
    const speakers = this.getSpeakers();
    if (paragraph.uid !== speaker.uid) {
      this.controller.updateUserByPid(pid, speaker.uid);

      const newSpeakers = getBeforeChangeSpeakers(
        this.controller,
        speakers,
        [paragraph.pid],
        speaker.uid
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);

        this.controller.emit("speakerNameChange", {
          uid: speaker.uid,
        });
      }
    }
  }

  updateOneByInputText(pid: PID, oldInputText: string, inputText: string) {
    if (oldInputText === inputText || !inputText) {
      return;
    }
    const paragraph = this.controller.getParagraph(pid);
    const speakers = this.getSpeakers();
    const speaker = findSpeakerByUid(speakers, paragraph.uid!);
    const sameNameSpeaker = findNotAlibabaSpeakerByName(
      speakers,
      inputText,
      this.getDefaultSpeakerName
    );

    if (sameNameSpeaker && sameNameSpeaker.uid !== paragraph.uid) {
      const newSpeakers = getBeforeChangeSpeakers(
        this.controller,
        speakers,
        [paragraph.pid],
        sameNameSpeaker.uid
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateUserByPid(pid, sameNameSpeaker.uid);
      }
    } else if (!speaker || speaker.name !== inputText) {
      const uid = generateSpeakerUid(speakers);
      const beforeUid = this.controller.getUserByPid(pid);
      const addedSpeaker: Speaker = {
        ui: beforeUid,
        uid,
        name: inputText,
      };
      const newSpeakers = getBeforeChangeSpeakers(
        this.controller,
        speakers,
        [paragraph.pid],
        uid
      ).concat(addedSpeaker);
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateUserByPid(pid, uid);
      }
    }
  }

  updateManyByAlibabaEmployee(currentPid: PID, alibabaEmployee: AlibabaEmployee) {
    const paragraph = this.controller.getParagraph(currentPid);
    const speakers = this.getSpeakers();
    const pids = this.controller.getParagraphsByUid(paragraph.uid!).map(p => p.pid);

    const speaker = findSpeakerByBizUid(speakers, alibabaEmployee.bizUid);

    if (speaker) {
      const { uid } = speaker;
      const updatedSpeaker: Speaker = {
        uid,
        name: getDisplayAlibabaEmployeeName(alibabaEmployee),
        avatarUrl: alibabaEmployee.avatarUrl,
        bizUid: alibabaEmployee.bizUid,
      };
      const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, pids, uid);

      const pos = findSpeakerPosByBizUid(newSpeakers, alibabaEmployee.bizUid);

      newSpeakers[pos] = updatedSpeaker;

      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateAllUsers(pids, uid);
      }
    } else {
      const uid = generateSpeakerUid(speakers);
      const addedSpeaker: Speaker = {
        uid,
        name: getDisplayAlibabaEmployeeName(alibabaEmployee),
        avatarUrl: alibabaEmployee.avatarUrl,
        bizUid: alibabaEmployee.bizUid,
      };

      const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, pids, uid).concat(
        addedSpeaker
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateAllUsers(pids, uid);
      }
    }
  }

  updateManyByHistorySpeaker(currentPid: PID, speaker: Speaker) {
    const speakers = this.getSpeakers();
    const paragraph = this.controller.getParagraph(currentPid);
    const pids = this.controller.getParagraphsByUid(paragraph.uid!).map(p => p.pid);

    const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, pids, speaker.uid);
    if (this.isVaildSpeakers(newSpeakers)) {
      this.setSpeakers(newSpeakers);
      this.controller.updateAllUsers(pids, speaker.uid);
    }
  }

  updateManyByInputText(currentPid: PID, oldInputText: string, inputText: string) {
    if (oldInputText === inputText || !inputText) {
      return;
    }
    const speakers = this.getSpeakers();
    const paragraph = this.controller.getParagraph(currentPid);
    const pids = this.controller.getParagraphsByUid(paragraph.uid!).map(p => p.pid);

    const speaker = findSpeakerByUid(speakers, paragraph.uid!);
    const sameNameSpeaker = findNotAlibabaSpeakerByName(
      speakers,
      inputText,
      this.getDefaultSpeakerName
    );
    if (sameNameSpeaker) {
      const newSpeakers = getBeforeChangeSpeakers(
        this.controller,
        speakers,
        pids,
        sameNameSpeaker.uid
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateAllUsers(pids, sameNameSpeaker.uid);
      }
    } else if (speaker) {
      if (speaker.name === inputText) {
        const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, pids, speaker.uid);
        if (this.isVaildSpeakers(newSpeakers)) {
          this.setSpeakers(newSpeakers);
          this.controller.updateAllUsers(pids, speaker.uid);
        }
      } else {
        const uid = generateSpeakerUid(speakers);
        const newSpeaker: Speaker = {
          uid,
          name: inputText,
        };
        const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, pids, uid).concat(
          newSpeaker
        );
        if (this.isVaildSpeakers(newSpeakers)) {
          this.setSpeakers(newSpeakers);
          this.controller.updateAllUsers(pids, uid);
        }
      }
    } else {
      const uid = generateSpeakerUid(speakers);
      const addedSpeaker: Speaker = {
        uid,
        name: inputText,
      };
      const newSpeakers = getBeforeChangeSpeakers(this.controller, speakers, pids, uid).concat(
        addedSpeaker
      );
      if (this.isVaildSpeakers(newSpeakers)) {
        this.setSpeakers(newSpeakers);
        this.controller.updateAllUsers(pids, uid);
      }
    }
  }

  renderSpeakerName(uid?: UID) {
    if (uid) {
      const speakers = this.getSpeakers() || [];
      const speaker = getSpeaker(uid, speakers);
      const _name = getSpeakerName(speaker, this.getDefaultSpeakerName);
      return _name;
    } else {
      return getSpeakerName(undefined, this.getDefaultSpeakerName);
    }
  }

  renderSpeakerHighlight(uid?: UID) {
    if (uid) {
      const speakers = this.getSpeakers() || [];
      const speaker = getSpeaker(uid, speakers);
      const isHighlight = getSpeakerHighlight(speaker);
      return isHighlight;
    }
    return false;
  }

  getSpeaker(uid: UID) {
    if (uid) {
      const speakers = this.getSpeakers() || [];
      const speaker = getSpeaker(uid, speakers);
      return speaker;
    }
  }

  reset() {
    this.tempRemovedSpeakers = {};
    this.historySpeakerMap = {};
  }
}
