import { WID } from "@tingwujs/core";
import { ViewedWord } from "./viewedWord";
import { isEqual } from "lodash-es";
import { isFirefox } from "@tingwujs/common";

export interface ActionPatchMap {
  appendWords: {
    words: ViewedWord[];
  };
  deleteAfterWords: {
    wid: WID;
  };
  updateWord: {
    wid: WID;
    word: ViewedWord;
  };
  updateAllWords: {
    words: ViewedWord[];
  };
}

type PatchAction = keyof ActionPatchMap;
type Patch<A = string, P = any> = [A, P];
type PatchMap = {
  [A in PatchAction]: Patch<A, ActionPatchMap[A]>;
};
export type AnyPatch = PatchMap[PatchAction];

const getDifferentKeys = (aWord: ViewedWord, bWord: ViewedWord) => {
  const keys = Object.keys(Object.assign({}, aWord, bWord)) as Array<keyof ViewedWord>;
  const differentKeys: Array<keyof ViewedWord> = [];
  keys.forEach(key => {
    if (!isEqual(aWord[key], bWord[key])) {
      differentKeys.push(key);
    }
  });
  return differentKeys;
};

const isSameWord = (aword: ViewedWord, bWord: ViewedWord) => {
  const keys = getDifferentKeys(aword, bWord).filter(
    key => !["beginTime", "endTime", "sentenceId", "nFix"].includes(key)
  );
  return keys.length === 0;
};

export const generateViewedWordDiff = () => {
  const store: ViewedWord[][] = [];
  const ret = {
    push(viewedWord: ViewedWord[]) {
      if (store.length === 2) {
        store.shift();
      }
      store.push(viewedWord);
      return this;
    },
    diff(): AnyPatch[] {
      const patchs: AnyPatch[] = [];
      const [prevWords, nextWords] = store;
      if (!nextWords) {
        return [
          [
            "updateAllWords",
            {
              words: prevWords,
            },
          ],
        ];
      }

      const prevLength = prevWords.length;
      const nextLength = nextWords.length;

      if (nextLength > prevLength) {
        for (let i = 0; i < nextLength; i++) {
          const prevWord = prevWords[i];
          const nextWord = nextWords[i];
          if (prevWord && nextWord) {
            if (isSameWord(prevWord, nextWord)) {
              continue;
            } else {
              break;
            }
          }

          return [
            [
              "appendWords",
              {
                words: nextWords.slice(i),
              },
            ],
          ];
        }
      }

      if (prevLength !== nextLength) {
        return [
          [
            "updateAllWords",
            {
              words: nextWords,
            },
          ],
        ];
      }

      if (nextLength === 1 && nextWords[0].text === "") {
        return [
          [
            "updateAllWords",
            {
              words: nextWords,
            },
          ],
        ];
      }

      let prevWid: WID | undefined;
      let appendNextWord = false;
      for (let i = 0; i < prevLength; i++) {
        const prevWord = prevWords[i];
        const nextWord = nextWords[i];

        if (prevWord.wid !== nextWord.wid) {
          if (prevWid === undefined) {
            return [
              [
                "updateAllWords",
                {
                  words: nextWords,
                },
              ],
            ];
          }
          patchs.push(
            [
              "deleteAfterWords",
              {
                wid: prevWord.wid,
              },
            ],
            [
              "appendWords",
              {
                words: nextWords.slice(i),
              },
            ]
          );
        } else if (!isEqual(prevWord, nextWord)) {
          const differentKeys = getDifferentKeys(prevWord, nextWord);
          const blackList = differentKeys.filter(
            key => !["beginTime", "endTime", "sentenceId"].includes(key)
          );
          if (blackList.length > 0) {
            patchs.push([
              "updateWord",
              {
                wid: nextWord.wid,
                word: nextWord,
              },
            ]);
          }
          if (!appendNextWord && isFirefox()) {
            appendNextWord = true;
            continue;
          }
        } else if (appendNextWord) {
          patchs.push([
            "updateWord",
            {
              wid: nextWord.wid,
              word: nextWord,
            },
          ]);
        }
        appendNextWord = false;
        prevWid = prevWord.wid;
      }
      return patchs;
    },
  };

  return ret;
};
