import { AnyOperation, MoveWordPayload, PID, UID, WID, Word } from "../../persist";
import type { Controller } from "..";
import { Setter, Book } from "./type";
import {
  generateDeleteTextRangeOps,
  generateInsertTextOps,
  generateAddParagraphOps,
  generateMergeParagraphOps,
  generateRemoveParagraphOps,
  generateFixParagraph,
  generateAddUnfixParagraph,
  generateAppendWords,
  generateSetParagraphMark,
  generateUpdateUserByPid,
  generateUpdateAllUsers,
  generateUpdateWords,
  generateUpdateExistWords,
  insertTextAndAddParagraph,
} from "../../utils";
import { Point, Range, Selection } from "../../editor";

export const createSetterBook = (controller: Controller, apply: Setter["apply"]): Book => {
  return {
    insertText: (pid: PID, startOffset: number, text: string) => {
      const ops = generateInsertTextOps(controller, pid, startOffset, text);
      const point = Point.create(pid, startOffset + text.length);
      const nextSelection = Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      );
      apply(ops, nextSelection);
    },
    insertTextAndAddParagraph: (pid: PID, startOffset: number, text: string) => {
      const ops = insertTextAndAddParagraph(controller, pid, startOffset, text);
      let userFixedPid: PID | undefined;
      let nextSelection: Selection | undefined;
      const addParagraphOp = ops.find(op => op.action === "addParagraph");
      if (addParagraphOp?.action === "addParagraph") {
        userFixedPid = addParagraphOp.payload.pid;

        const addWordOp = ops.find(op => op.action === "addWord");
        const moveWordOp = ops.find(op => op.action === "moveWord");
        if (addWordOp) {
          const offset = startOffset + text.length;
          const point = Point.create(userFixedPid, offset);
          nextSelection = Selection.create(
            Range.create({
              start: point,
              end: point,
            })
          );
        } else if (moveWordOp) {
          const newWids = (moveWordOp.payload as MoveWordPayload).targetNewOrder;

          const newWordsLength = newWids.reduce((prev, wid) => {
            const word = controller.getWord(wid);
            return prev + word.text.length;
          }, 0);

          const offset = Math.max(0, startOffset + text.length - newWordsLength);
          const point = Point.create(pid, offset);
          nextSelection = Selection.create(
            Range.create({
              start: point,
              end: point,
            })
          );
        }
      }

      return apply(ops, nextSelection, { userFixedPid });
    },
    deleteTextRange: (range: Range) => {
      const ops = generateDeleteTextRangeOps(controller, range);

      const point = range.start;

      const nextSelection = Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      );

      apply(ops, nextSelection);
    },
    addParagraph: (prevPid: PID, newPid: PID, startOffset: number) => {
      const ops = generateAddParagraphOps(controller, prevPid, newPid, startOffset);

      let nextSelection: Selection | undefined;
      const addParagraphOp = ops.find(op => op.action === "addParagraph");
      if (addParagraphOp?.action === "addParagraph") {
        const point = Point.create(addParagraphOp.payload.pid, 0);
        nextSelection = Selection.create(
          Range.create({
            start: point,
            end: point,
          })
        );
      }
      apply(ops, nextSelection!);
    },

    mergeParagraph: (prevPid: PID, pid: PID) => {
      const ops = generateMergeParagraphOps(controller, prevPid, pid);
      const text = controller.getTextContent(prevPid);

      const point = Point.create(prevPid, text.length);

      const nextSelection = Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      );

      apply(ops, nextSelection);
    },

    removeParagraph: (pid: PID) => {
      const ops = generateRemoveParagraphOps(controller, pid);
      const pids = controller.getPids();
      const pos = pids.indexOf(pid);

      const nextPid = pids[pos + 1];
      let nextSelection: Selection | undefined;
      if (nextPid != null) {
        const point = Point.create(nextPid, 0);
        nextSelection = Selection.create(
          Range.create({
            start: point,
            end: point,
          })
        );
      }

      apply(ops, nextSelection);
    },

    addUnfixParagraph: (pid: PID, uid: UID, targetWids?: WID[]) => {
      const ops = generateAddUnfixParagraph(controller, pid, uid, targetWids);
      // console.log('addUnfixParagraph', ops);
      apply(ops);
    },
    addFixParagraph: (pid: PID, targetWids?: WID[], selection?: Selection) => {
      const ops = generateFixParagraph(controller, pid, targetWids);
      // console.log('addFixParagraph', ops);
      apply(ops, selection);
    },
    appendWords: (pid: PID, words: Word[], willFix = false) => {
      const ops = generateAppendWords(controller, pid, words, willFix);
      // console.log('appendWords', ops);
      apply(ops);
    },
    updateExistWords: (words: Word[]) => {
      const ops = generateUpdateExistWords(controller, words);
      apply(ops);
    },
    setParagraphMark: (pid: PID, tagColor: string) => {
      const ops = generateSetParagraphMark(controller, pid, tagColor);
      // console.log('setParagraphMark', ops);
      apply(ops);
    },
    updateUserByPid: (pid: PID, uid: UID) => {
      const ops = generateUpdateUserByPid(controller, pid, uid);
      apply(ops);
    },
    updateAllUsers: (pids: PID[], uid: UID) => {
      const ops = generateUpdateAllUsers(controller, pids, uid);
      apply(ops);
    },
    updateParagraphWords: (paragraphWords: Map<PID, Word[]>, attrs: Partial<Word>) => {
      const ops: AnyOperation[] = [];
      // console.log('debug updateParagraphWords', paragraphWords);
      paragraphWords.forEach((words, pid) => {
        ops.push(...generateUpdateWords(pid, words, attrs));
      });
      // console.log('debug ops', ops);
      apply(ops);
    },
  };
};
