import type { Controller, Speaker, AlibabaEmployee } from "../controller";
import { UID, PID } from "../persist";
import { startsWith } from "lodash-es";

export const getSpeaker = (uid: UID, speakers: Speaker[]): Speaker | undefined => {
  const found = speakers.find(speaker => speaker.uid === uid || speaker.ui === uid);
  return found;
};

export const defaultSpeakerName = "发言人";
export const firstSpeaker: Speaker = {
  uid: "1",
  name: "",
};

export const getSpeakerName = (
  speaker: Speaker | undefined,
  getDefaultName: (speaker?: Speaker) => string
) => {
  return (speaker && speaker.displayName) || getDefaultName(speaker);
};

// 获取当前发言人是否高亮
export const getSpeakerHighlight = (speaker: Speaker | undefined) => {
  return speaker && speaker.isHighlight;
};

export const UID_START_WORD = "c";

export const isUserGeneratedSpeaker = (speaker: Speaker) => {
  return startsWith(speaker.uid, UID_START_WORD);
};

export const isAlibabaEmployeeSpeaker = (speaker: Speaker) => {
  return Boolean(speaker.bizUid);
};

let globalSpeakerUid = 1;
const getNextGeneratedUid = (speakers: Speaker[]) => {
  let id = 0;
  speakers
    .filter(speaker => isUserGeneratedSpeaker(speaker))
    .forEach(speaker => {
      const match = speaker.uid.match(/\d+$/);
      if (match) {
        const parsedId = parseInt(match[0], 10);
        if (parsedId > id) {
          id = parsedId;
        }
      }
    });

  ++id;

  if (id > globalSpeakerUid) {
    globalSpeakerUid = id;
  } else {
    ++globalSpeakerUid;
  }

  return globalSpeakerUid;
};

export const generateSpeakerUid = (speakers: Speaker[]) => {
  const id = getNextGeneratedUid(speakers);
  return `${UID_START_WORD}${id}`;
};

export const getDisplayAlibabaEmployeeName = (employee: AlibabaEmployee) => {
  return `${employee.name}${employee.nickName ? `(${employee.nickName})` : ""}`;
};

export const findSpeakerPosByBizUid = (speakers: Speaker[], bizUid: string) => {
  const { length } = speakers;
  for (let i = 0; i < length; i++) {
    const speaker = speakers[i];
    if (speaker.bizUid === bizUid) {
      return i;
    }
  }
  return -1;
};

export const findSpeakerByBizUid = (speakers: Speaker[], bizUid: string) => {
  return speakers[findSpeakerPosByBizUid(speakers, bizUid)];
};

export const findSpeakerPosByUid = (speakers: Speaker[], uid: UID) => {
  const { length } = speakers;
  for (let i = 0; i < length; i++) {
    const speaker = speakers[i];
    if (speaker.ui === uid || speaker.uid === uid) {
      return i;
    }
  }
  return -1;
};

export const findSpeakerByUid = (speakers: Speaker[], uid: UID) => {
  return speakers[findSpeakerPosByUid(speakers, uid)];
};

export const findNotAlibabaSpeakerPosByName = (
  speakers: Speaker[],
  name: string,
  getDefaultSpeakerName: (speaker?: Speaker) => string
) => {
  const { length } = speakers;
  for (let i = 0; i < length; i++) {
    const speaker = speakers[i];
    const speakerName = speaker.name || getDefaultSpeakerName(speaker);
    const isAlibaba = isAlibabaEmployeeSpeaker(speaker);
    if (!isAlibaba && speakerName === name) {
      return i;
    }
  }
  return -1;
};

export const findNotAlibabaSpeakerByName = (
  speakers: Speaker[],
  name: string,
  getDefaultSpeakerName: (speaker?: Speaker) => string
) => {
  return speakers[findNotAlibabaSpeakerPosByName(speakers, name, getDefaultSpeakerName)];
};

export const getBeforeChangeSpeakers = (
  controller: Controller,
  currentSpeakers: Speaker[],
  targetPids: PID[],
  newUid?: UID
) => {
  const otherParagraphs = controller
    .getParagraphs()
    .filter(paragraph => targetPids.indexOf(paragraph.pid) === -1);

  const uids = otherParagraphs.map(paragraph => paragraph.uid);

  const tempUid = controller.getEmptyUnfixTempUid();
  if (tempUid) {
    uids.push(tempUid);
  }

  if (newUid) {
    uids.push(newUid);
  }

  const newSpeakers: Speaker[] = [];

  currentSpeakers.forEach(speaker => {
    if (uids.includes(speaker.uid)) {
      newSpeakers.push(speaker);
    }
  });
  return newSpeakers;
};

export const createSpeakerChangeListener = (
  controller: Controller,
  onUserChanged: (allChangeUids: Record<UID, UID>) => void,
  onUserAnyChanged?: () => void
) => {
  const getCurrentPidUidMap = () => {
    const paragraphs = controller.getParagraphs();
    const currentPrevPidUidMap: Record<PID, UID> = {};
    paragraphs.forEach(paragraph => {
      if (paragraph.uid !== undefined) {
        currentPrevPidUidMap[paragraph.pid] = paragraph.uid;
      }
    });
    return currentPrevPidUidMap;
  };

  const diff = (prev: Record<PID, UID>, next: Record<PID, UID>) => {
    const userChangedLog: Record<
      UID,
      {
        changes: Record<UID, boolean>;
        notChanges: number;
      }
    > = {};

    const allChangeUids: Record<UID, UID> = {};
    let isAnyUserChanged = false;

    const prevKeys = Object.keys(prev);
    if (prevKeys.length !== Object.keys(next).length) {
      isAnyUserChanged = true;
      return [allChangeUids, isAnyUserChanged] as const;
    }

    prevKeys.forEach(key => {
      const prevUid = prev[key];
      const nextUid = next[key];

      if (!prevUid || !nextUid) {
        return;
      }

      if (!userChangedLog[prevUid]) {
        userChangedLog[prevUid] = {
          changes: {},
          notChanges: 0,
        };
      }

      if (prevUid === nextUid) {
        userChangedLog[prevUid].notChanges++;
      } else {
        userChangedLog[prevUid].changes[nextUid] = true;
      }
    });

    const uids = Object.keys(userChangedLog);
    uids.forEach(uid => {
      const log = userChangedLog[uid];
      const { changes, notChanges } = log;
      const changesKeys = Object.keys(changes);

      if (changesKeys.length === 1 && notChanges === 0) {
        allChangeUids[uid] = changesKeys[0];
      }
      if (changesKeys.length) {
        isAnyUserChanged = true;
      }
    });

    return [allChangeUids, isAnyUserChanged] as const;
  };

  const cleans: Array<() => void> = [];
  let prevPidUidMap: Record<PID, UID> = getCurrentPidUidMap();

  cleans.push(
    controller.on("modelValueChange", () => {
      prevPidUidMap = getCurrentPidUidMap();
    }),
    controller.on("paragraphChangeSpecific", () => {
      const currentPidUidMap = getCurrentPidUidMap();
      const [userChanged, isAnyUserChanged] = diff(prevPidUidMap, currentPidUidMap);
      if (Object.keys(userChanged).length > 0) {
        onUserChanged(userChanged);
      }
      if (isAnyUserChanged) {
        onUserAnyChanged && onUserAnyChanged();
      }

      prevPidUidMap = currentPidUidMap;
    })
  );

  return () => {
    cleans.forEach(clean => {
      clean();
    });
  };
};
