import React, { memo, useCallback, useEffect, useMemo, useRef } from "react";
import {
  Controller,
  Selection,
  Point,
  Range,
  getCaretIndexByGoDown,
  getPosAndEdge,
  getCaretIndexByGoUp,
  getEffectKeys,
} from "@tingwu/core";
import { Wrapper } from "./styled";
import { isBackspaceHotkey, isDeleteHotkey, isEnterHotkey, isSpaceHotKey } from "@tingwu/common";
import { isMacOS } from "@tingwu/util";
import { isEqual } from "lodash-es";
import { FirefoxCache } from "../textEditor/utils";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { caretStyleState, composingDataState, isReadonlyState } from "../../model";
import { TranscriptionController } from "../../controller";
import { useScrollView } from "../../scrollView";

export interface TextBoxProps {
  controller: Controller;
  transController: TranscriptionController;
  scrollViewRef: React.RefObject<HTMLDivElement>;
  theme: any;
}

export const TextBox = memo((props: TextBoxProps) => {
  const { controller, transController, scrollViewRef, theme } = props;
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const setComposingData = useSetRecoilState(composingDataState);
  const caretStyle = useRecoilValue(caretStyleState);
  const isReadyonly = useRecoilValue(isReadonlyState);

  const selectionStyleRef = useRef<{ top: number; left: number; pid: string } | undefined>();
  const isFocusedRef = useRef(false);

  const { containerHeight } = useScrollView({
    controller,
    transController,
    theme,
  });

  const { textBoxStyle } = useMemo(() => {
    isFocusedRef.current = false;
    const result = { textBoxStyle: { top: 0, left: 0 } };

    if (!scrollViewRef.current) {
      return result;
    }

    const scrollView = scrollViewRef.current;
    const scrollViewRect = scrollView.getBoundingClientRect();

    const fixStyleTop = () => {
      if (result.textBoxStyle.top > containerHeight) {
        result.textBoxStyle.top = containerHeight;
      }
    };

    // caretStyle为undefined的时候会导致scrollView盒子置顶，需要设置top，使textBox在视野内
    if (!caretStyle) {
      result.textBoxStyle.top = scrollView.scrollTop;

      if (!isReadyonly && selectionStyleRef.current) {
        const { pid, top, left } = selectionStyleRef.current;
        const editorDom = controller.getParagraphDOM(pid);
        if (editorDom) {
          const { top: editorTop, left: editorLeft } = editorDom.getBoundingClientRect();

          result.textBoxStyle.top = top + editorTop - scrollViewRect.top + scrollView.scrollTop + 4;
          result.textBoxStyle.left = left + editorLeft - scrollViewRect.left;
        }
      }

      fixStyleTop();
      return result;
    }

    isFocusedRef.current = true;
    const { style, editorRect } = caretStyle;
    const offset = editorRect.top - scrollViewRect.top + scrollView.scrollTop;

    result.textBoxStyle.top = offset + parseInt(String(style.top), 10);
    result.textBoxStyle.left =
      editorRect.left - scrollViewRect.left + parseInt(String(style.left), 10);

    return result;
  }, [caretStyle, scrollViewRef, controller, isReadyonly, containerHeight]);

  const currentRef = useRef<{
    isOnComposition: boolean;
    composingData: string;
    blurTimeout: ReturnType<typeof setTimeout> | null;
  }>({
    isOnComposition: false,
    composingData: "",
    blurTimeout: null,
  });

  // !!!删除指定range的文字，不要在Backspace和Delete场景下使用
  const handleDeleteRange = useCallback(
    (range: Range, isPreteat = false) => {
      if (isEqual(range.start, range.end)) {
        return;
      }
      if (isPreteat) {
        controller.preteat();
      }
      controller.deleteTextRange(range);
    },
    [controller]
  );

  const handleTextInput = useCallback(
    (text: string) => {
      const selection = controller.getCurrentSelection();
      if (!selection) {
        return;
      }
      const { offset, pid } = selection.range.start;
      if (controller.isGreaterThenMaxCount(pid, offset, text)) {
        controller.insertTextAndAddParagraph(pid, offset, text);
      } else {
        controller.insertText(pid, offset, text);
      }
    },
    [controller]
  );

  const deleteBeforeAction = useCallback(() => {
    const selection = controller.getCurrentSelection();
    if (!selection) {
      return;
    }
    handleDeleteRange(selection.range, true);
  }, [controller, handleDeleteRange]);

  const handleFocus = useCallback(() => {
    if (currentRef.current.blurTimeout) {
      clearTimeout(currentRef.current.blurTimeout);
      currentRef.current.blurTimeout = null;
    }
  }, []);

  const handleBlur = useCallback(() => {
    // currentRef.current.blurTimeout = setTimeout(() => {
    //   controller.setCurrentSelection(undefined);
    // }, 400);
    controller.setCurrentSelection(undefined);

    transController.emit("onDropdownHideOff", {});
  }, [controller, transController]);

  const handleCompositionStart = useCallback(() => {
    currentRef.current.isOnComposition = true;
    deleteBeforeAction();
  }, [deleteBeforeAction]);

  const compositionEndCacheRef = useRef(new FirefoxCache());

  const handleCompositionEnd = useCallback(
    (event: React.CompositionEvent<HTMLTextAreaElement>) => {
      currentRef.current.isOnComposition = false;
      compositionEndCacheRef.current?.setContent(event.data);
      handleTextInput(event.data);
      setComposingData((currentRef.current.composingData = ""));

      // 输入法键入结束，将光标暂时隐藏，不然会出现光标移动的问题
      controller.emit("caretStyleChange", { value: undefined });
    },
    [handleTextInput, setComposingData, controller]
  );

  const handleBeforeInput = useCallback(() => {
    if (currentRef.current.isOnComposition) {
      return;
    }

    deleteBeforeAction();
  }, [deleteBeforeAction]);

  const handleInput = useCallback(
    (event: React.SyntheticEvent<HTMLTextAreaElement, InputEvent>) => {
      const text = event.nativeEvent.data;
      if (currentRef.current.isOnComposition) {
        setComposingData((currentRef.current.composingData = text || ""));
        return;
      }

      if (!text) {
        // in windows, undo will trigger this function, and pass empty text
        return;
      }

      if (
        compositionEndCacheRef.current &&
        compositionEndCacheRef.current.isCompositeBefore(text)
      ) {
        return;
      }

      handleTextInput(text);
    },
    [handleTextInput, setComposingData]
  );

  const handleGoLeft = useCallback(() => {
    const selection = controller.getCurrentSelection();
    if (!selection) {
      return;
    }

    const { start } = selection.range;
    controller.handleTextboxInput();
    let point = start;
    if (selection.isCaret()) {
      point = Point.create(start.pid, Math.max(start.offset - 1, 0));
    }

    controller.setCurrentSelection(
      Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      )
    );
    controller.handleTextboxInput();
  }, [controller]);

  const handleGoRight = useCallback(() => {
    const selection = controller.getCurrentSelection();
    if (!selection) {
      return;
    }

    const { end } = selection.range;
    controller.handleTextboxInput();
    const textContent = controller.getTextContent(end.pid);
    let point = end;
    if (selection.isCaret()) {
      point = Point.create(end.pid, Math.min(end.offset + 1, textContent.length));
    }

    controller.setCurrentSelection(
      Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      )
    );
    controller.handleTextboxInput();
  }, [controller]);

  const handleGoUp = useCallback(() => {
    const selection = controller.getCurrentSelection();
    if (!selection) {
      return;
    }

    const { pid, offset } = selection.range.end;

    const paragraphDOM = controller.getParagraphDOM(pid);
    if (!paragraphDOM) {
      return;
    }

    const position = getCaretIndexByGoUp(controller, pid, paragraphDOM, offset);
    const [newOffset, edge] = getPosAndEdge(position);
    if (newOffset === offset) {
      return;
    }

    const point = Point.create(pid, newOffset, edge);

    controller.setCurrentSelection(
      Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      )
    );
    controller.handleTextboxInput();
  }, [controller]);

  const handleGoDown = useCallback(() => {
    const selection = controller.getCurrentSelection();
    if (!selection) {
      return;
    }

    const { pid, offset } = selection.range.end;

    const paragraphDOM = controller.getParagraphDOM(pid);
    if (!paragraphDOM) {
      return;
    }

    const position = getCaretIndexByGoDown(controller, pid, paragraphDOM, offset);

    const [newOffset, edge] = getPosAndEdge(position);
    if (newOffset === offset) {
      return;
    }

    const point = Point.create(pid, newOffset, edge);

    controller.setCurrentSelection(
      Selection.create(
        Range.create({
          start: point,
          end: point,
        })
      )
    );
    controller.handleTextboxInput();
  }, [controller]);

  const handleKeyDown = useCallback(
    (event: React.KeyboardEvent) => {
      const { nativeEvent } = event;
      const inputKey = nativeEvent.key;
      const selection = controller.getCurrentSelection();
      if (!selection) {
        return;
      }

      const { start, end } = selection.range;
      const { pid } = start;
      const isCaret = isEqual(start, end);
      const isUnfix = start.pid === end.pid && controller.getUnfixPid() === start.pid;
      const textContent = controller.getTextContent(pid);

      if (!currentRef.current.isOnComposition) {
        if (inputKey === "ArrowLeft") {
          handleGoLeft();
          return;
        }

        if (inputKey === "ArrowRight") {
          handleGoRight();
          return;
        }

        if (inputKey === "ArrowDown") {
          handleGoDown();
          return;
        }

        if (inputKey === "ArrowUp") {
          handleGoUp();
          return;
        }
      }

      if (isMacOS) {
        if (nativeEvent.altKey && nativeEvent.keyCode === 32) {
          event.preventDefault();
          return;
        }
        if (nativeEvent.metaKey) {
          if (inputKey === "a") {
            const newSelection = Selection.create(
              Range.create({
                start: Point.create(pid, 0),
                end: Point.create(pid, textContent.length),
              })
            );
            controller.setCurrentSelection(newSelection);
            event.preventDefault();
          } else if (inputKey === "b" || inputKey === "e") {
            // command+e 上层需要用
            event.preventDefault();
            return;
          } else if (inputKey === "s") {
            event.preventDefault();
            controller.emitSaveDocument();
            return;
          }
        }

        if (nativeEvent.ctrlKey) {
          if (inputKey === "k") {
            event.preventDefault();
            handleDeleteRange(selection.range);
            return;
          } else if (inputKey === "d") {
            event.preventDefault();
            if (isCaret) {
              if (textContent.length === 0) {
                return;
              }

              handleDeleteRange(
                Range.create({
                  start,
                  end: Point.create(start.pid, start.offset + 1, start.edge, start.isUnfix),
                })
              );
              event.preventDefault();
            } else {
              handleDeleteRange(selection.range);
            }
            return;
          } else if (inputKey === "s") {
            event.preventDefault();
            controller.emitSaveDocument();
            return;
          }
        }
      } else if (nativeEvent.shiftKey) {
        if (nativeEvent.keyCode === 32) {
          event.preventDefault();
          return;
        }
      } else if (nativeEvent.ctrlKey) {
        if (
          inputKey === "e" ||
          inputKey === "b" ||
          inputKey === "ArrowUp" ||
          inputKey === "ArrowLeft" ||
          inputKey === "ArrowDown" ||
          inputKey === "ArrowRight"
        ) {
          event.preventDefault();
          return false;
        } else if (inputKey === "z") {
          controller.undo();
        } else if (inputKey === "y") {
          controller.redo();
        } else if (inputKey === "s") {
          event.preventDefault();
          controller.emitSaveDocument();
        }
      }

      // if (currentRef.current.isOnComposition) {
      //   return;
      // }

      if (isSpaceHotKey(event.keyCode)) {
        event.stopPropagation();
      } else if (isEnterHotkey(event.keyCode)) {
        event.preventDefault();
        if (!isUnfix) {
          deleteBeforeAction();
          controller.addParagraph(pid, start.offset);
        }
      } else if (isDeleteHotkey(event.keyCode)) {
        event.preventDefault();

        if (isCaret) {
          if (start.offset === textContent.length) {
            // TODO: Forward Delete will merge next Paragraph???
          } else {
            controller.deleteTextRange(
              Range.create({
                start,
                end: Point.create(end.pid, end.offset + 1),
              })
            );
          }
        } else {
          controller.deleteTextRange(selection.range);
        }
      } else if (isBackspaceHotkey(event.keyCode)) {
        event.preventDefault();

        if (isCaret) {
          if (start.offset === 0) {
            if (!isUnfix) {
              const pids = controller.getPids();
              if (pids.length > 1 && pids[0] === start.pid && textContent.length === 0) {
                controller.removeParagraph(pid);
              } else {
                controller.mergePrevParagraph(pid);
              }
            }
          } else {
            controller.deleteTextRange(
              Range.create({
                start: Point.create(end.pid, end.offset - 1),
                end,
              })
            );
          }
        } else {
          controller.deleteTextRange(selection.range);
        }
      } else if (inputKey === "z") {
        if (event.nativeEvent.metaKey) {
          event.preventDefault();

          if (event.nativeEvent.shiftKey) {
            controller.redo();
          } else {
            controller.undo();
          }
        }
      } else if (inputKey === "y") {
        if (event.nativeEvent.metaKey) {
          event.preventDefault();
          controller.redo();
        }
      }
    },
    [
      controller,
      deleteBeforeAction,
      handleDeleteRange,
      handleGoLeft,
      handleGoRight,
      handleGoDown,
      handleGoUp,
    ]
  );

  const handleCut = useCallback(
    (event: React.ClipboardEvent) => {
      event.preventDefault();
      const selection = controller.getCurrentSelection();
      if (!selection) {
        return;
      }

      const textContents = controller.getTextContentBySelection();
      event.clipboardData.setData("text/plain", textContents.join("\n"));
      handleDeleteRange(selection.range);
    },
    [handleDeleteRange, controller]
  );

  const handleCopy = useCallback(
    (event: React.ClipboardEvent) => {
      event.preventDefault();
      const textContents = controller.getTextContentBySelection();
      event.clipboardData.setData("text/plain", textContents.join("\n"));
    },
    [controller]
  );

  const handlePaste = useCallback(
    (event: React.ClipboardEvent) => {
      event.preventDefault();
      let text = event.clipboardData.getData("text/plain");
      const selection = controller.getCurrentSelection();
      if (!selection || !text) {
        return;
      }

      text = text.replace(/\n/, "");
      const { pid, offset } = selection.range.start;
      deleteBeforeAction();
      if (controller.isGreaterThenMaxCount(pid, offset, text)) {
        controller.insertTextAndAddParagraph(pid, offset, text);
      } else {
        controller.insertText(pid, offset, text);
      }
    },
    [controller, deleteBeforeAction]
  );

  useEffect(() => {
    setTimeout(() => {
      if (!textareaRef.current || !textBoxStyle || !isFocusedRef.current) {
        return;
      }
      textareaRef.current.focus({
        preventScroll: true,
      });
    });
  }, [textBoxStyle]);

  useEffect(() => {
    controller.on("focusEditor", () => {
      if (!textareaRef.current) {
        return;
      }
      textareaRef.current.focus({
        preventScroll: true,
      });
    });
    controller.on("selectionStylesChange", ({ styles }) => {
      const startPid = Object.keys(styles).sort()[0];
      const style = styles[startPid]?.[0];
      if (style) {
        selectionStyleRef.current = {
          top: Number(style.top),
          left: Number(style.left),
          pid: startPid,
        };
      }
    });

    const onMouseUp = (event: MouseEvent) => {
      const selection = controller.getCurrentSelection();

      if (selection) {
        // 音字回听
        controller.emit("mouseUpSelectionChange", { selection });

        // 划选弹窗
        if (selection.isCaret()) {
          transController.emit("onDropdownHideOff", {});
        } else if (!currentRef.current.blurTimeout) {
          const startPid = selection.range.start.pid;
          const endPid = selection.range.end.pid;
          const pids =
            startPid === endPid ? [startPid] : getEffectKeys(controller.getPids(), selection.range);

          transController.emit("onDropdownShowUp", {
            clientX: event.clientX,
            clientY: event.clientY,
            rangeRect: new DOMRect(),
            pids,
          });
        }
      }
    };
    document.addEventListener("mouseup", onMouseUp);

    return () => {
      document.removeEventListener("mouseup", onMouseUp);
    };
  }, [controller, transController]);

  const getEventHandler = useCallback(
    <T extends (event: any) => any>(fn: T): T | undefined => {
      if (isReadyonly) {
        return undefined;
      }
      return fn;
    },
    [isReadyonly]
  );

  if (textBoxStyle) {
    return (
      <Wrapper style={textBoxStyle}>
        <textarea
          readOnly={isReadyonly}
          ref={textareaRef}
          onFocus={handleFocus}
          onBlur={handleBlur}
          onBeforeInput={getEventHandler(handleBeforeInput)}
          onInput={getEventHandler(handleInput)}
          onCompositionStart={getEventHandler(handleCompositionStart)}
          onCompositionEnd={getEventHandler(handleCompositionEnd)}
          onKeyDown={getEventHandler(handleKeyDown)}
          onCopy={handleCopy}
          onPaste={getEventHandler(handlePaste)}
          onCut={getEventHandler(handleCut)}
        />
      </Wrapper>
    );
  }

  return null;
});
