import { cloneDeep } from "lodash-es";
import { AnyOperation, createOperation, PID, WID, UID, Word } from "../persist";
import type { Controller } from "../controller";
import { generateInsertTextOps, getAvibleNfixingWids, getNfixingAndFixedWids } from "./text";
import { report, reportAem } from "./report";

export const generatePid = (prevPid?: PID, nextPid?: PID) => {
  if (!prevPid && nextPid) {
    // 最顶部段落，顶部添加
    const n1 = nextPid.slice(0, 13);
    const n2 = nextPid.slice(13);
    const nr = String(parseInt(n2, 10) / 2);
    // 原字符串为 000xxx，转化数字时缺失部分位数，计算结果长度小于原字符串，需补位
    return `${n1}${"0".repeat(n2.length - nr.length)}${nr}`;
  }

  if (prevPid && !nextPid) {
    // 最尾部段落，尾部添加
    const c1 = prevPid.slice(0, 10);
    const c2 = prevPid.slice(10);
    const nr = String(parseInt(c2, 10) + 500000);
    let prev = c1;
    let next = nr;
    // 原字符串为 000xxx，转化数字时缺失部分位数，计算结果长度小于原字符串，需补位
    if (nr.length < c2.length) {
      next = `${"0".repeat(c2.length - nr.length)}${nr}`;
    }

    // 发生进位，c1 需要加1，同时需补位，nr需要去除多余的第一位
    if (nr.length > c2.length) {
      const pr = String(parseInt(c1, 10) + 1);
      prev = `${"0".repeat(c1.length - pr.length)}${pr}`;
      next = `${nr.slice(1)}`;
    }
    return `${prev}${next}`;
  }

  // 前后都有段落，中间段添加
  if (prevPid && nextPid) {
    const c1 = parseInt(prevPid.slice(0, 10), 10);
    const c2 = parseInt(prevPid.slice(10), 10);

    const n1 = parseInt(nextPid.slice(0, 10), 10);
    let n2 = parseInt(nextPid.slice(10), 10);

    let nr = String(Math.floor((c2 + n2) / 2));

    // prevPid&nextPid同位时，只需处理后缀
    let pr = `${c1}`;

    // nextPid > prevPid时
    if (n1 > c1) {
      // let pr = Math.floor((c1 + n1) / 2);
      // n2计算时，需要将前缀的差位补上
      n2 = parseInt(String(n1 - c1) + nextPid.slice(10), 10);
      nr = String(Math.floor((c2 + n2) / 2));
      const nlen = nr.length - (nextPid.length - 10);
      // 后缀位，平均后，仍有进位
      if (nlen > 0) {
        pr = String(c1 + parseInt(nr.slice(0, nlen)));
        nr = nr.slice(nlen);
      }
    }

    const prev = `${"0".repeat(10 - pr.length)}${pr}`;
    // 后缀位可能存在00xxx字符串，需补位
    const next = `${"0".repeat(prevPid.length - 10 - nr.length)}${nr}`;

    return `${prev}${next}`;
  }

  // 前后都无段落，空段添加
  return `${+new Date()}500000`;
};

let tokenSeed = +new Date();
export const generateWid = () => {
  return String(tokenSeed++ * 10 + 1); // 客户端生成的ID以1结尾，服务端以0结尾
};

export const generateAddParagraphOps = (
  controller: Controller,
  prevPid: PID,
  newPid: PID,
  startOffset: number
) => {
  const paragraph = controller.getParagraph(prevPid);
  const words = controller.getWords(prevPid);
  const ops: AnyOperation[] = [];

  const newParagraph = cloneDeep(paragraph);
  newParagraph.pid = newPid;
  newParagraph.wids = [];
  delete newParagraph.isUnfix;

  ops.push(
    createOperation("addParagraph", {
      prevPid,
      pid: newPid,
      paragraph: newParagraph,
    })
  );

  let offset = 0;
  const toMoveIds: WID[] = [];
  let newParagraphOrder: WID[] = [];
  let oldParagraphOrder: WID[] = paragraph.wids;

  words.forEach(word => {
    const startAt = offset;
    offset += word.text.length;
    if (startOffset < offset) {
      if (startOffset > startAt) {
        const newWordToUpdate = cloneDeep(word);
        newWordToUpdate.text = word.text.slice(0, startOffset - startAt);

        const newWordToAdd = cloneDeep(word);
        newWordToAdd.wid = generateWid();
        newWordToAdd.text = word.text.slice(startOffset - startAt);

        ops.push(
          createOperation("updateWord", {
            pid: prevPid,
            wid: word.wid,
            oldValue: word,
            newValue: newWordToUpdate,
          }),
          createOperation("addWord", {
            pid: newPid,
            wid: newWordToAdd.wid,
            word: newWordToAdd,
            oldOrder: newParagraphOrder,
            newOrder: (newParagraphOrder = newParagraphOrder.concat(newWordToAdd.wid)),
          })
        );
      } else {
        toMoveIds.push(word.wid);
      }
    }
  });

  if (toMoveIds.length > 0) {
    ops.push(
      createOperation("moveWord", {
        originPid: prevPid,
        targetPid: newPid,
        originOldOrder: oldParagraphOrder,
        originNewOrder: (oldParagraphOrder = oldParagraphOrder.filter(
          wid => toMoveIds.indexOf(wid) === -1
        )),
        targetOldOrder: newParagraphOrder,
        targetNewOrder: (newParagraphOrder = newParagraphOrder.concat(toMoveIds)),
      })
    );

    // move all words
    if (toMoveIds.length === words.length) {
      const firstWord = words[0];
      const newWord = cloneDeep(firstWord);
      newWord.wid = generateWid();
      newWord.text = "";
      newWord.beginTime = firstWord.beginTime;
      newWord.endTime = firstWord.beginTime;
      ops.push(
        createOperation("addWord", {
          pid: prevPid,
          wid: newWord.wid,
          word: newWord,
          oldOrder: oldParagraphOrder,
          newOrder: (oldParagraphOrder = oldParagraphOrder.concat(newWord.wid)),
        })
      );
    }
  }

  // press `Enter` by tail -- no addWord, no updateWord, no moveWord
  if (ops.length === 1) {
    const lastWord = words[words.length - 1];
    const newWord = cloneDeep(lastWord);
    newWord.wid = generateWid();
    newWord.text = "";
    newWord.beginTime = lastWord.endTime;
    newWord.endTime = lastWord.endTime;
    ops.push(
      createOperation("addWord", {
        pid: newPid,
        wid: newWord.wid,
        word: newWord,
        oldOrder: newParagraphOrder,
        newOrder: (newParagraphOrder = newParagraphOrder.concat(newWord.wid)),
      })
    );
  }

  return ops;
};

export const getPreviousPid = (controller: Controller, pid: PID) => {
  const pids = controller.getPids();
  const pos = pids.indexOf(pid);
  if (pos !== -1) {
    return pids[pos - 1] || undefined;
  }
  return undefined;
};

export const generateMergeParagraphOps = (controller: Controller, prevPid: PID, pid: PID) => {
  const ops: AnyOperation[] = [];
  if (!prevPid) {
    return ops;
  }
  const prevParagraph = controller.getParagraph(prevPid);
  const currentParagraph = controller.getParagraph(pid);
  const { wids } = currentParagraph;

  if (controller.getTextContent(pid).length === 0 && wids.length === 1) {
    ops.push(
      createOperation("deleteWord", {
        pid,
        wid: wids[0],
        word: controller.getWord(wids[0]),
        oldOrder: currentParagraph.wids,
        newOrder: [],
      })
    );
  } else {
    ops.push(
      createOperation("moveWord", {
        originPid: currentParagraph.pid,
        originOldOrder: currentParagraph.wids,
        originNewOrder: [],
        targetPid: prevParagraph.pid,
        targetOldOrder: prevParagraph.wids,
        targetNewOrder: prevParagraph.wids.concat(currentParagraph.wids),
      })
    );
  }

  ops.push(
    createOperation("removeParagraph", {
      prevPid: prevParagraph.pid,
      pid: currentParagraph.pid,
      paragraph: currentParagraph,
    })
  );

  return ops;
};

export const generateRemoveParagraphOps = (controller: Controller, pid: PID) => {
  const ops: AnyOperation[] = [];
  const pids = controller.getPids();
  const pos = pids.indexOf(pid);

  if (pos === -1) {
    return ops;
  }

  const prevPid = pos === 0 ? undefined : pids[pos - 1];
  const paragraph = controller.getParagraph(pid);

  const { wids } = paragraph;
  let tempOrder = wids.slice();
  wids.forEach(wid => {
    const word = controller.getWord(wid);
    const oldOrder = tempOrder;
    const newOrder = tempOrder.splice(1);
    tempOrder = newOrder;
    ops.push(
      createOperation("deleteWord", {
        pid,
        wid,
        word,
        oldOrder,
        newOrder,
      })
    );
  });

  ops.push(
    createOperation("removeParagraph", {
      prevPid,
      pid,
      paragraph,
    })
  );

  return ops;
};

export const generateFixParagraph = (controller: Controller, pid: PID, targetWids?: WID[]) => {
  const ops: AnyOperation[] = [];
  const paragraph = controller.getParagraph(pid);

  if (paragraph.isUnfix) {
    let originWids = paragraph.wids.filter(
      wid => !targetWids || targetWids.length === 0 || targetWids.indexOf(wid) > -1
    );
    // @ts-expect-error 忽略类型判定
    originWids = originWids.sort((a, b) => a - b); // 时序保障

    let newWids = originWids;
    const { nfixingWids } = getAvibleNfixingWids(controller, paragraph.wids);

    if (nfixingWids.length > 0) {
      // 成段时剔除nfixing的word
      newWids = originWids.filter(wid => !nfixingWids.includes(wid));
      controller.pushTempNfixingWids(nfixingWids);
      report("push nfixingWids", {
        nfixingWids,
      });
    }

    const isEmptyContent =
      newWids.length === 0 || (newWids.length === 1 && !controller.getWord(newWids[0]).text);
    if (isEmptyContent) {
      reportAem("rmOnAddFixPg", {
        paragraph: JSON.stringify(paragraph),
      });
    }

    if (isEmptyContent) {
      const pids = controller.getPids().filter(p => p !== pid);
      const prevPid = pids.length > 0 ? pids[pids.length - 1] : undefined;
      ops.push(
        createOperation("removeParagraph", {
          prevPid,
          pid,
          paragraph,
        })
      );
    } else {
      ops.push(
        createOperation("updateParagraph", {
          pid,
          oldParagraph: paragraph,
          paragraph: {
            ...paragraph,
            wids: newWids,
            isUnfix: false,
          },
        })
      );
    }
  }
  return ops;
};

export const generateAddUnfixParagraph = (
  controller: Controller,
  pid: PID,
  uid: UID,
  targetWids?: WID[]
) => {
  const ops: AnyOperation[] = [];

  const pids = controller.getPids();
  const prevPid = pids.length === 0 ? undefined : pids[pids.length - 1];
  if (prevPid) {
    const fixOps = generateFixParagraph(controller, prevPid);
    if (fixOps.length > 0) {
      ops.push(...fixOps);
    }
  }

  // 避免words添加之前被删掉，导致wid和words不同步。
  let wids = controller.popTempNfixingWids().filter(wid => {
    return controller.getWord(wid);
  });

  if (targetWids && targetWids.length > 0) {
    const tempWids = wids.filter(wid => targetWids.indexOf(wid) === -1);

    wids = [
      ...tempWids,
      ...targetWids.filter(wid => {
        return Boolean(controller.getWord(wid));
      }),
    ];
  }

  report("popTempNfixingWids", {
    wids,
  });

  ops.push(
    createOperation("addParagraph", {
      prevPid,
      pid,
      paragraph: {
        pid,
        wids,
        uid,
        isUnfix: true,
      },
    })
  );
  return ops;
};

export const generateAppendWords = (
  controller: Controller,
  pid: PID,
  words: Word[],
  willFix = false
) => {
  const ops: AnyOperation[] = [];
  const paragraph = controller.getParagraph(pid);

  const wids = paragraph.wids.slice();
  const needMoveWids: WID[] = [];
  const addWordWaiting: Word[] = [];

  words.forEach((word, idx) => {
    const isAppeared = controller.isAppearedWid(word.wid);
    const isLastWord = idx === words.length - 1;
    if (isAppeared) {
      const oldWord = controller.getWord(word.wid);
      // 当有旧词时更新
      if (oldWord) {
        if (wids.includes(word.wid)) {
          ops.push(
            createOperation("updateWord", {
              pid,
              wid: word.wid,
              oldValue: oldWord,
              newValue: {
                ...oldWord,
                sentenceId: word.sentenceId,
                beginTime: word.beginTime,
                endTime: word.endTime,
                nFix: word.nFix,
                // 成段时，最后一个词内容更新(解决：)
                ...(willFix && isLastWord
                  ? {
                      text: word.text,
                    }
                  : {}),
              },
            })
          );
        } else {
          needMoveWids.push(word.wid);
        }
      }
    } else {
      addWordWaiting.push(word);
    }
  });

  const targetPargraph = controller.getParagraph(pid);
  let targetOldOrder = targetPargraph.wids;

  (() => {
    if (needMoveWids.length > 0) {
      // must be in one paragraph
      const originPid = controller.getPidByWid(needMoveWids[0]);
      if (!originPid) {
        return;
      }
      const originPargraph = controller.getParagraph(originPid);
      let originOldOrder = originPargraph.wids;

      needMoveWids.forEach(wid => {
        const originNewOrder = originOldOrder.filter(_wid => _wid !== wid);
        const targetNewOrder = targetOldOrder.concat(wid);

        ops.push(
          createOperation("moveWord", {
            originPid,
            originOldOrder,
            originNewOrder,
            targetPid: pid,
            targetOldOrder,
            targetNewOrder,
          })
        );

        originOldOrder = originNewOrder;
        targetOldOrder = targetNewOrder;
      });
    }
  })();
  (() => {
    if (addWordWaiting.length > 0) {
      addWordWaiting.forEach(word => {
        ops.push(
          createOperation("addWord", {
            pid,
            wid: word.wid,
            word,
            oldOrder: targetOldOrder,
            newOrder: (targetOldOrder = targetOldOrder.concat(word.wid)),
          })
        );
      });
    }
  })();

  return ops;
};

export const generateUpdateExistWords = (controller: Controller, words: Word[]) => {
  const ops: AnyOperation[] = [];
  words.forEach(word => {
    const isAppeared = controller.isAppearedWid(word.wid);
    if (isAppeared) {
      const oldWord = controller.getWord(word.wid);
      const pid = controller.getPidByWid(word.wid);
      // 当有旧词时更新
      if (oldWord && pid) {
        ops.push(
          createOperation("updateWord", {
            pid,
            wid: word.wid,
            oldValue: oldWord,
            newValue: {
              ...oldWord,
              // text: word.text, // 直接更新可能会有 bug
              sentenceId: word.sentenceId,
              beginTime: word.beginTime,
              endTime: word.endTime,
              nFix: word.nFix,
            },
          })
        );
      }
    }
  });
  return ops;
};

export const generateSetParagraphMark = (controller: Controller, pid: PID, tagColor: string) => {
  const ops: AnyOperation[] = [];
  const paragraph = controller.getParagraph(pid);

  if (!paragraph) {
    return [];
  }

  const oldWords: Word[] = [];
  const newWords: Word[] = [];

  paragraph.wids.forEach(wid => {
    const word = controller.getWord(wid);
    if (!word) {
      return;
    }
    const newWord = cloneDeep(word);
    newWord.tag = tagColor;

    oldWords.push(word);
    newWords.push(newWord);
  });

  if (newWords.length > 0) {
    ops.push(
      createOperation("updateWords", {
        pid,
        oldValue: oldWords,
        newValue: newWords,
      })
    );
  }

  return ops;
};

export const generateUpdateUserByPid = (controller: Controller, pid: PID, uid: UID) => {
  const ops: AnyOperation[] = [];
  const paragraph = controller.getParagraph(pid);
  ops.push(
    createOperation("updateParagraph", {
      pid,
      oldParagraph: paragraph,
      paragraph: {
        ...paragraph,
        uid,
      },
    })
  );
  return ops;
};

export const generateUpdateAllUsers = (controller: Controller, pids: PID[], uid: UID) => {
  const ops: AnyOperation[] = [];
  pids.forEach(pid => {
    const paragraph = controller.getParagraph(pid);
    ops.push(
      createOperation("updateParagraph", {
        pid,
        oldParagraph: paragraph,
        paragraph: {
          ...paragraph,
          uid,
        },
      })
    );
  });
  return ops;
};

export const insertTextAndAddParagraph = (
  controller: Controller,
  pid: PID,
  startOffset: number,
  text: string
) => {
  const ops = generateInsertTextOps(controller, pid, startOffset, text);

  const pids = controller.getPids();
  const pos = pids.indexOf(pid);
  const prevPid: string | undefined = pids[pos - 1];

  const newPid = generatePid(prevPid, pid);

  const paragraph = controller.getParagraph(pid);
  const newParagraph = cloneDeep(paragraph);
  newParagraph.pid = newPid;
  newParagraph.wids = [];
  delete newParagraph.isUnfix;

  const { nfixingWids } = getNfixingAndFixedWids(controller, paragraph.wids);

  const originOldOrder = paragraph.wids;
  const originNewOrder = paragraph.wids.filter(wi => nfixingWids.includes(wi));
  const targetOldOrder = newParagraph.wids;
  const targetNewOrder = paragraph.wids.filter(wi => !nfixingWids.includes(wi));

  if (targetNewOrder.length > 0) {
    ops.push(
      createOperation("addParagraph", {
        prevPid,
        pid: newPid,
        paragraph: newParagraph,
      })
    );

    ops.push(
      createOperation("moveWord", {
        originPid: pid,
        originOldOrder,
        originNewOrder,
        targetPid: newPid,
        targetOldOrder,
        targetNewOrder,
      })
    );
  }

  if (originNewOrder.length === 0) {
    const wid = paragraph.wids[paragraph.wids.length - 1];
    const word = controller.getWord(wid);
    const newWord: Word = {
      ...word,
      wid: generateWid(),
      text: "",
      beginTime: word.endTime,
      tag: "",
    };

    ops.push(
      createOperation("addWord", {
        pid,
        wid: newWord.wid,
        word: newWord,
        oldOrder: [],
        newOrder: [newWord.wid],
      })
    );
  }

  return ops;
};
