import { cloneDeep, isEqual } from "lodash-es";
import type { Controller } from "../controller";
import { createOperation, PID, WID, AnyOperation, Word, SID } from "../persist";
import { Range } from "../editor";
import { getEffectKeys } from "./editor";

export const insertWordByPos = (text: string, word: string, pos: number) => {
  return [text.slice(0, pos), word, text.slice(pos)].join("");
};

export const getIndexByReverseIndex = (index: number, length: number) => {
  return length - index - 1;
};

const getOrdersByDeleteId = (wids: WID[], deleteWid: WID) => {
  return {
    oldOrder: wids,
    newOrder: wids.filter(wid => wid !== deleteWid),
  };
};

export const getIsSelectAllText = (
  controller: Controller,
  pid: PID,
  startOffset: number,
  endOffset: number
) => {
  const textContent = controller.getTextContent(pid);
  return textContent.length === endOffset - startOffset && startOffset === 0;
};

export const generateDeleteTextRangeOps = (controller: Controller, range: Range) => {
  const ops: AnyOperation[] = [];

  if (isEqual(range.start, range.end)) {
    return ops;
  }

  const startPid = range.start.pid;
  const endPid = range.end.pid;
  const effectPids = startPid === endPid ? [startPid] : getEffectKeys(controller.getPids(), range);

  effectPids.forEach(pid => {
    const isSelectStart = startPid === pid;
    const isSelectEnd = endPid === pid;
    const isSelectMiddle = !isSelectStart && !isSelectEnd;

    if (isSelectMiddle) {
      ops.push(
        createOperation("removeParagraph", {
          prevPid: startPid,
          pid,
          paragraph: controller.getParagraph(pid),
        })
      );
      return;
    }

    const words = controller.getWords(pid);
    const textContent = controller.getTextContent(pid);
    const paragraph = controller.getParagraph(pid);
    let wids = paragraph.wids.slice();
    let offset = 0;

    const startOffset = isSelectStart ? range.start.offset : 0;
    const endOffset = isSelectEnd ? range.end.offset : textContent.length;
    const isSelectAllText = getIsSelectAllText(controller, pid, startOffset, endOffset);

    for (let index = 0; index < words.length; index++) {
      const word = words[index];
      offset += word.text.length;
      const wordStartAt = offset - word.text.length;
      const oldValue = word.text;

      if (endOffset < wordStartAt || startOffset > offset) {
        continue;
      } else if (startOffset <= wordStartAt && endOffset >= offset) {
        if (isSelectAllText) {
          if (isSelectStart) {
            const newWord = cloneDeep(word);
            const newParagraph = cloneDeep(paragraph);

            newWord.text = "";
            newParagraph.wids = [newWord.wid];
            ops.push(
              createOperation("updateWord", {
                pid,
                wid: word.wid,
                oldValue: word,
                newValue: newWord,
              }),
              createOperation("updateParagraph", {
                pid,
                oldParagraph: paragraph,
                paragraph: newParagraph,
              })
            );
          } else {
            ops.push(
              createOperation("removeParagraph", {
                prevPid: startPid,
                pid,
                paragraph,
              })
            );
          }
          break;
        } else {
          const { oldOrder, newOrder } = getOrdersByDeleteId(wids, word.wid);
          wids = newOrder;
          ops.push(
            createOperation("deleteWord", {
              pid,
              wid: word.wid,
              word,
              oldOrder,
              newOrder,
            })
          );
        }
      } else {
        let newValue = "";
        if (startOffset > wordStartAt && endOffset < offset) {
          newValue = [
            oldValue.slice(0, startOffset - wordStartAt),
            oldValue.slice(endOffset - wordStartAt),
          ].join("");
        } else if (startOffset > wordStartAt && startOffset < offset) {
          newValue = oldValue.slice(0, startOffset - wordStartAt);
        } else if (endOffset < offset && endOffset > wordStartAt) {
          newValue = oldValue.slice(endOffset - wordStartAt);
        }

        if (newValue) {
          const newWord = cloneDeep(word);
          newWord.text = newValue;
          ops.push(
            createOperation("updateWord", {
              pid,
              wid: word.wid,
              oldValue: word,
              newValue: newWord,
            })
          );
        }
      }
    }
  });

  return ops;
};

export const generateInsertTextOps = (
  controller: Controller,
  pid: PID,
  startOffset: number,
  text: string
) => {
  const words = controller.getWords(pid);
  let offset = 0;
  const ops: AnyOperation[] = [];

  words.some(word => {
    offset += word.text.length;
    if (startOffset <= offset) {
      const oldValue = word.text;
      const pos = startOffset - (offset - oldValue.length);
      const newValue = [oldValue.slice(0, pos), text, oldValue.slice(pos)].join("");
      const newWord = cloneDeep(word);
      newWord.text = newValue;

      ops.push(
        createOperation("updateWord", {
          pid,
          wid: word.wid,
          oldValue: word,
          newValue: newWord,
          userChangeText: true,
        })
      );
      return true;
    }
    return false;
  });

  return ops;
};

export const generateUpdateWords = (pid: PID, words: Word[], attrs: Partial<Word>) => {
  const ops: AnyOperation[] = [];
  const oldWords: Word[] = [];
  const newWords: Word[] = [];
  words.forEach(word => {
    oldWords.push(word);
    newWords.push({
      ...word,
      ...attrs,
    });
  });

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

  return ops;
};

export const getSidWidsMap = (controller: Controller, wids: WID[]) => {
  const sidWidsMap: Record<SID, { isNfixing: boolean; wids: WID[] }> = {};
  const sids: SID[] = [];
  wids.forEach(wid => {
    const word = controller.getWord(wid);
    if (!word) {
      return;
    }
    if (!sidWidsMap[word.sentenceId]) {
      sidWidsMap[word.sentenceId] = {
        isNfixing: false,
        wids: [],
      };
      sids.push(word.sentenceId);
    }

    sidWidsMap[word.sentenceId].wids.push(wid);
    if (word.nFix) {
      sidWidsMap[word.sentenceId].isNfixing = true;
    }
  });
  return { sidWidsMap, sids };
};

export const getUnfixSidWidsMap = (controller: Controller) => {
  let result: ReturnType<typeof getSidWidsMap> = {
    sidWidsMap: {},
    sids: [],
  };
  const unfixPid = controller.getUnfixPid();

  if (unfixPid) {
    const paragraph = controller.getParagraph(unfixPid);

    result = getSidWidsMap(controller, paragraph.wids);
  }

  return result;
};

export const getNfixingAndFixedWids = (controller: Controller, wids: WID[]) => {
  const { sidWidsMap, sids } = getSidWidsMap(controller, wids);
  const nfixingWids: WID[] = [];
  const fixedWids: WID[] = [];

  sids.forEach(sid => {
    if (sidWidsMap[sid].isNfixing) {
      nfixingWids.push(...sidWidsMap[sid].wids);
    } else {
      fixedWids.push(...sidWidsMap[sid].wids);
    }
  });

  return { nfixingWids, fixedWids };
};

export const getAvibleNfixingWids = (controller: Controller, wids: WID[]) => {
  const { nfixingWids, fixedWids } = getNfixingAndFixedWids(controller, wids);

  const filteredFixedWids = fixedWids.filter(wid => wid.length < 10); // The wid great than 10  that user generated.

  if (filteredFixedWids.length === 0) {
    return { nfixingWids };
  }
  const lastFixedWid = filteredFixedWids[filteredFixedWids.length - 1];

  return {
    nfixingWids: nfixingWids.filter(wid => Number(wid) > Number(lastFixedWid)),
  };
};
