import React, { useCallback, useEffect, useRef, useImperativeHandle } from "react";
import { clearRange, useSyncEffect } from "@tingwujs/common";
import { Controller, PID, Point, Range, Selection, walkElementToFindOffset } from "@tingwujs/core";
import { Wrapper } from "./styled";
import { ViewedWord } from "../../model";
import { ViewedWords } from "./renderViewedWord";
import { Caret } from "./caret";
import { HSelection } from "./hSelection";
import { throttle } from "lodash-es";
import { QnAController } from "@tingwujs/sphere/src/spheres";

export interface TextEditorProps {
  pid: PID;
  textContent?: string;
  controller: Controller;
  timestamp?: string;
  isUnfix?: boolean;
  viewedWords: ViewedWord[];
  isHighlight?: boolean;
  readonly?: boolean;
  isPlaying?: boolean;
  modifyDefaultFontColor?: string; // 修改字体的默认颜色
  qaController?: QnAController;
  meetingAgendaActive?: boolean; // 章节速览回溯
}

export interface TextEditorHandler {
  focus: () => void;
  blur: () => void;
}

export const TextEditor = React.forwardRef<TextEditorHandler, TextEditorProps>((props, ref) => {
  const {
    pid,
    textContent = "",
    isUnfix = false,
    controller,
    qaController,
    viewedWords,
    isHighlight = false,
    readonly = false,
    isPlaying = false,
    modifyDefaultFontColor = "",
    meetingAgendaActive = false,
  } = props;

  const containerRef = useRef<HTMLDivElement>(null);
  const handleMouseMoveThrottleRef = useRef<ReturnType<typeof throttle>>();
  useSyncEffect(() => {
    return controller.on("blurCurrentTextEditor", () => {
      clearRange();
      containerRef.current?.blur();
    });
  }, [controller]);

  const handleDragStart = useCallback((event: React.DragEvent) => {
    event.preventDefault();
  }, []);

  const handleDrop = handleDragStart;

  useImperativeHandle(ref, () => {
    return {
      focus: () => {
        containerRef.current?.focus();
      },
      blur: () => {
        containerRef.current?.blur();
      },
      setIsPlaying: (isPlay: boolean) => {
        /** 未声明 */
        // setPlay(isPlay)
      },
    };
  }, []);

  useEffect(() => {
    if (!readonly) {
      return;
    }
    const handleClickSpan = (event: MouseEvent) => {
      if (!event.target) {
        return;
      }
      const element = event.target as HTMLElement;
      if (element?.nodeName === "SPAN") {
        const wid = element.getAttribute("data-word-id");
        if (!wid) {
          return;
        }
        const word = controller.getWord(wid);
        if (word) {
          controller.readonlyClickWord([word]);
        }
      }
    };
    containerRef.current?.addEventListener("mouseup", handleClickSpan);
    return () => {
      containerRef.current?.removeEventListener("mouseup", handleClickSpan);
    };
  }, [readonly, controller]);

  const handleMouseDown = useCallback(
    (event: React.MouseEvent) => {
      if (!containerRef.current) {
        return;
      }
      event.preventDefault();

      if (event.button !== 0) {
        return;
      }

      switch (event.detail) {
        case 1: {
          const [pos, edge] = walkElementToFindOffset(
            controller,
            pid,
            containerRef.current,
            event.clientX,
            event.clientY
          );

          const point = Point.create(pid, pos, edge);
          const selection = controller.getCurrentSelection();
          qaController?.setActiveQnaIndex(-1);
          controller.setMeetingActiveAgendaById(undefined);
          if (event.shiftKey && selection) {
            controller.mergeSelectionAt(selection, point);
          } else {
            controller.startSelectionAt(point);
          }

          break;
        }
        case 2: {
          controller.clearPendingPoint();
          const [pos] = walkElementToFindOffset(
            controller,
            pid,
            containerRef.current,
            event.clientX,
            event.clientY
          );

          const range = controller.getWordRangeByOffset(pid, pos);
          if (range) {
            const s = Selection.create(range);
            controller.setCurrentSelection(s);
          }
          break;
        }

        case 3:
        default: {
          controller.clearPendingPoint();
          const s = Selection.create(
            Range.create({
              start: Point.create(pid, 0),
              end: Point.create(pid, textContent.length),
            })
          );
          controller.setCurrentSelection(s);
          break;
        }
      }
    },
    [controller, pid, textContent, qaController]
  );

  const handleMouseMove = useCallback(
    (e: React.MouseEvent) => {
      handleMouseMoveThrottleRef.current = throttle((event: React.MouseEvent) => {
        if (!containerRef.current || !controller.isSelectionPending(isUnfix)) {
          return;
        }

        if (event.buttons === 0) {
          controller.endSelectionAt();
          return;
        }

        const [pos, edge] = walkElementToFindOffset(
          controller,
          pid,
          containerRef.current,
          event.clientX,
          event.clientY
        );

        controller.pendingSelectionAt(Point.create(pid, pos, edge));
      }, 16.6);

      handleMouseMoveThrottleRef.current(e);
    },
    [controller, isUnfix, pid]
  );

  // 鼠标划选离开编辑区，选中末尾或开头文字
  const handleMouseLeave = useCallback(
    (event: React.MouseEvent) => {
      if (!containerRef.current || !controller.isSelectionPending(isUnfix)) {
        return;
      }

      if (event.buttons === 0) {
        controller.endSelectionAt();
        return;
      }

      const { clientY } = event;
      const { length: textContentLength } = controller.getTextContent(pid);

      const elementRect = containerRef.current.getBoundingClientRect();

      if (clientY <= elementRect.top >> 0) {
        controller.pendingSelectionAt(Point.create(pid, 0, "after"));
        handleMouseMoveThrottleRef.current && handleMouseMoveThrottleRef.current.cancel();
      } else if (clientY >= elementRect.bottom >> 0) {
        controller.pendingSelectionAt(Point.create(pid, textContentLength, "after"));
        handleMouseMoveThrottleRef.current && handleMouseMoveThrottleRef.current.cancel();
      }
    },
    [controller, isUnfix, pid]
  );

  const handleMouseUp = useCallback(
    (event: React.MouseEvent) => {
      if (!containerRef.current || !controller.isSelectionPending(isUnfix)) {
        return;
      }

      const [pos, edge] = walkElementToFindOffset(
        controller,
        pid,
        containerRef.current,
        event.clientX,
        event.clientY
      );

      controller.endSelectionAt(Point.create(pid, pos, edge));
    },
    [controller, isUnfix, pid]
  );

  const handleContextMenu = useCallback((event: React.MouseEvent) => {
    event.preventDefault();
    event.stopPropagation();
  }, []);

  useEffect(() => {
    return controller.registerTextEditor(pid, () => {
      return {
        contentDOM: containerRef.current,
      };
    });
  }, [controller, pid]);

  useEffect(() => {
    controller.finishRender(pid);
  }, [viewedWords, controller, pid]);

  return (
    <Wrapper
      isPlaying={isPlaying}
      isHighlight={isHighlight}
      isUnfix={isUnfix}
      isEmptyContent={textContent.trim().length === 0} // 空白字符会导致点击不上
      ref={containerRef}
      readonly={readonly}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      onMouseUp={handleMouseUp}
      onDragStart={handleDragStart}
      onDrop={handleDrop}
      onContextMenu={handleContextMenu}
      draggable={false}
      spellCheck={false}
      modifyDefaultFontColor={modifyDefaultFontColor}
    >
      <ViewedWords
        controller={controller}
        pid={pid}
        words={viewedWords}
        isHighlight={isHighlight || (meetingAgendaActive && !isUnfix)}
      />
      <Caret
        controller={controller}
        pid={pid}
        containerRef={containerRef}
        isHighlight={isHighlight}
      />
      <HSelection
        controller={controller}
        pid={pid}
        containerRef={containerRef}
        isHighlight={isHighlight}
      />
    </Wrapper>
  );
});
