/* eslint-disable react-hooks/rules-of-hooks */

import { useSyncEffect } from "@tingwujs/common";
import { Controller } from "@tingwujs/core";
import { throttle } from "lodash-es";
import React, { useState, useCallback, useRef, useEffect, useMemo, Fragment } from "react";
import useForceUpdate from "use-force-update";
import { generateGetJustFixPid } from "../../utils";
import { TranscriptionController, TranscriptionEventes } from "../../controller";
import { AutoScrollButton, ToTopButton } from "./styled";
import { scrollEaseOutAnimation } from "../../scrollView/scrollEaseOut";
import { TYIcon, Tooltip } from "@tingwujs/design";

interface extensionProps {
  controller: Controller;
  tranController: TranscriptionController;
  getTranscriptionElem: () => Element | null;
  getContainerElem: () => Element | null;
  type?: string;
  rootClassName?: string;
}

export interface RenderContentProps {
  onWhell?: () => void;
  onScroll?: () => void;
  isShowingLivingScrollToBottom?: boolean;
  onScrollToBottom?: any;
}

export interface ExtendProps extends RenderContentProps {
  renderContent: (props: RenderContentProps) => React.ReactNode;
  onDisplayWordShowUp: (event: TranscriptionEventes["onDisplayWordShowUp"]) => void;
  onContainerRectChange: (rect: DOMRectReadOnly) => void;
}

type ExtendPropsFunc<T = ExtendProps> = (props: T) => T;

type ExtensionKeys =
  | "extendByPlayingWord"
  | "extendByCheckAgenda"
  | "extendByNothing"
  | "extendByLivingScrollEnd";

export type GenerateExtension<T = ExtendProps> = (
  extParams: extensionProps
) => Record<ExtensionKeys, ExtendPropsFunc<T>>;

export const generateExtension: GenerateExtension<ExtendProps> = (extParams: extensionProps) => {
  const {
    controller,
    tranController,
    getTranscriptionElem,
    getContainerElem,
    type,
    rootClassName,
  } = extParams;
  const getHooks = () => {
    return tranController.getHooks();
  };
  let timer: any = null;

  return {
    extendByPlayingWord: props => {
      const { renderContent, onScroll, onDisplayWordShowUp } = props;
      const isEnabled = !controller.getLivingMode();
      const [isPlaying, setIsPlaying] = useState(false);
      const [userScrolled, setUserScrolled] = useState(false);
      const stateRef = useRef<{
        nextWordShowUp: boolean;
        scrollBySystem: boolean;
        scrollContainer: any;
        scrollContainerInner: any;
      }>({
        nextWordShowUp: false,
        scrollBySystem: false,
        scrollContainer: null,
        scrollContainerInner: null,
      });

      const handleClearUserScrolled = useCallback(() => {
        const { onEvent } = getHooks();
        onEvent && onEvent("clickReturnToPlayingWord", {});
        stateRef.current.nextWordShowUp = true;
        const [pid] = controller.getPlayingPidAndWord();

        const { playingWordController } = tranController;
        if (pid) {
          playingWordController.emit("paragraphShowUp", { pid, force: true });
        }
        setUserScrolled(false);
        stateRef.current.scrollBySystem = true;
      }, []);

      useSyncEffect(() => {
        if (!isEnabled) {
          return;
        }
        return controller.on("playingVoiceWordEnableChange", ({ enable }) => {
          setIsPlaying(enable);
          if (enable) {
            setUserScrolled(false);
          }
        });
      }, [isEnabled]);

      useSyncEffect(() => {
        if (!isEnabled) {
          return;
        }
        return controller.on("searchReplaceChange", ({ selections }) => {
          if (selections.length > 0) {
            setUserScrolled(true);
          }
        });
      }, [isEnabled]);

      useSyncEffect(() => {
        if (!isEnabled) {
          return;
        }
        return controller.on("editorFocusChange", () => {
          setUserScrolled(true);
        });
      }, [isEnabled]);

      const [playingChange, setPlayingChange] = useState(false);
      const [toNewVisible, setToNewVisible] = useState(false);
      const [toTopVisible, setToTopVisible] = useState(false);
      const [bottomNum, setBottomNum] = useState(18);
      // const [direction, setDirection] = useState<'top' | 'bottom'>();

      useEffect(() => {
        return controller.on("updateShortcutPositioningButtonBottom", (ev: { num: number }) => {
          setBottomNum(ev.num);
        });
      }, []);

      useEffect(() => {
        if (!isEnabled) {
          return;
        }
        const { playingWordController } = tranController;
        return playingWordController.on("playingParagraphVisibleChange", event => {
          if (event.enable) {
            // setDirection(event.direction!);
            setPlayingChange(!event.visible!);
            setToNewVisible(!event.visible!);
          } else {
            setPlayingChange(false);
            setToNewVisible(false);
          }
        });
      }, [isEnabled]);
      useEffect(() => {
        // 离线使用的按钮
        const scrollContainer = document.querySelector(
          rootClassName
            ? `.${rootClassName} #tingwu_paragraphs_scrollContainer`
            : "#tingwu_paragraphs_scrollContainer"
        );
        const scrollContainerInner = document.querySelector(
          rootClassName
            ? `.${rootClassName} #tingwu_paragraphs_scrollContainerInner`
            : "#tingwu_paragraphs_scrollContainerInner"
        );
        if (scrollContainer) {
          stateRef.current.scrollContainer = scrollContainer;
        }
        if (scrollContainerInner) {
          stateRef.current.scrollContainerInner = scrollContainerInner;
        }
      }, [rootClassName]);
      const ToTopView = React.useMemo(() => {
        if (toTopVisible) {
          if (controller.getLivingMode()) {
            return null;
          }
          const toTop = (e: any) => {
            e.stopPropagation();
            const { scrollContainer } = stateRef.current;
            // 滚动到顶部
            scrollContainer.scrollTop = 0;
            if (isEnabled) {
              setUserScrolled(true); // 停掉音字回听滚动
            }
            const { onEvent } = tranController.getHooks();
            onEvent && onEvent("clickBackToTop", {});
          };
          const bottom = isEnabled && toNewVisible ? bottomNum + 40 : bottomNum;
          return (
            <Tooltip
              title="回到顶部"
              placement={controller.getFileModel() === "box" ? "left" : "right"}
            >
              <ToTopButton onClick={toTop} bottom={bottom}>
                <TYIcon type="tongyi-top-line" />
              </ToTopButton>
            </Tooltip>
          );
        }
        return null;
      }, [toNewVisible, isEnabled, toTopVisible, bottomNum]);

      return {
        ...props,
        onScroll: useCallback(() => {
          if (scrollEaseOutAnimation.isAnimating) return;

          onScroll && onScroll();
          const { scrollContainerInner, scrollContainer } = stateRef.current;
          if (scrollContainerInner && scrollContainer) {
            const { top } = scrollContainerInner.getClientRects()[0];
            const { top: scrollContainerTop, height: scrollContainerHeight } =
              scrollContainer.getClientRects()[0];
            const scrollTop = Math.abs(top - scrollContainerTop);
            if (playingChange && scrollTop > 300) {
              setToNewVisible(true);
            }

            if (scrollTop > scrollContainerHeight) {
              setToTopVisible(true);
            } else {
              setToTopVisible(false);
            }
          }

          if (isEnabled) {
            if (!stateRef.current.scrollBySystem) {
              setUserScrolled(true);
            } else {
              stateRef.current.scrollBySystem = false;
            }
          }
        }, [isEnabled, toTopVisible, onScroll, playingChange]),

        renderContent: useCallback(
          _params => {
            if (isEnabled) {
              return (
                <>
                  {renderContent(_params)}
                  {ToTopView}
                  {toNewVisible && (
                    <Tooltip
                      title="回到当前播放位置"
                      placement={controller.getFileModel() === "box" ? "left" : "right"}
                    >
                      <AutoScrollButton
                        onClick={handleClearUserScrolled}
                        bottom={bottomNum}
                        data-e2e-test-id="trans_autoScroll_button"
                      >
                        <TYIcon type="extend-huidaobofangweizhi-targetPlayer-line" />
                      </AutoScrollButton>
                    </Tooltip>
                  )}
                </>
              );
            } else {
              return (
                <Fragment>
                  {renderContent(_params)}
                  {ToTopView}
                </Fragment>
              );
            }
          },
          [isEnabled, renderContent, bottomNum, toTopVisible, handleClearUserScrolled, toNewVisible]
        ),
        onDisplayWordShowUp: useCallback(
          (event: TranscriptionEventes["onDisplayWordShowUp"]) => {
            if (isEnabled) {
              const { isPlaying: _isPlaying, forceDisplay } = event;
              // 有段落激活的时候，不要滚动到对应playingWord
              if (_isPlaying && controller.getEditorFocusPid() != null) {
                return;
              }

              if (isPlaying) {
                if (_isPlaying && !stateRef.current.nextWordShowUp && userScrolled) {
                  if (!forceDisplay) {
                    return;
                  } else {
                    setUserScrolled(false);
                  }
                }
              }
            }
            stateRef.current.nextWordShowUp = false;
            stateRef.current.scrollBySystem = true;
            onDisplayWordShowUp && onDisplayWordShowUp(event);
          },
          [isEnabled, onDisplayWordShowUp, isPlaying, userScrolled]
        ),
      };
    },
    extendByCheckAgenda: props => {
      const { onScroll } = props;

      const isEnabled = controller.getEnableActiveAgenda();
      const forceUpdate = useForceUpdate();

      const lastActiveAgendaTimeRef = useRef<number>();

      useSyncEffect(() => {
        return controller.on("activeAgendaChange", () => {
          lastActiveAgendaTimeRef.current = new Date().getTime();
          forceUpdate();
        });
      }, [forceUpdate]);

      const checkDisplayAgendas = useCallback(
        throttle(() => {
          if (
            lastActiveAgendaTimeRef.current != null &&
            new Date().getTime() - lastActiveAgendaTimeRef.current < 1000
          ) {
            return;
          }
          const transcriptionElem = getTranscriptionElem();
          const containerElem = getContainerElem();
          if (!transcriptionElem || !containerElem) {
            return;
          }
          const containerRect = containerElem.getBoundingClientRect();
          const agendas = controller.getAgendas();
          agendas.some(agenda => {
            const foundsRects: Array<{
              rect: DOMRect;
              element: Element;
            }> = [];
            [].forEach.call(
              transcriptionElem.querySelectorAll(`[data-in-agenda-${agenda.id}]`),
              (element: Element) => {
                const rect = element.getBoundingClientRect();
                const elementTop = rect.top - containerRect.top + containerElem.scrollTop;
                if (
                  elementTop > containerElem.scrollTop &&
                  elementTop < containerElem.scrollTop + containerRect.height
                ) {
                  foundsRects.push({
                    rect,
                    element,
                  });
                }
              }
            );

            if (foundsRects.length === 0) {
              return false;
            }

            const lastFound = foundsRects[foundsRects.length - 1];
            const lastFoundTop = lastFound.rect.top - containerRect.top + containerElem.scrollTop;
            if (lastFoundTop > containerElem.scrollTop) {
              controller.detectedFirstShowUpAgenda(agenda);
              return true;
            }

            return false;
          });
        }, 500),
        []
      );

      return {
        ...props,
        onScroll: useCallback(() => {
          if (scrollEaseOutAnimation.isAnimating) return;

          onScroll && onScroll();
          if (isEnabled) {
            checkDisplayAgendas();
          }
        }, [isEnabled, onScroll, checkDisplayAgendas]),
      };
    },
    extendByLivingScrollEnd: props => {
      const { onScroll } = props;

      const [needScrollToEnd, setNeedScrollToEnd] = useState(true);
      const [isScrollEnd, setIsScrollEnd] = useState(true);
      const [isEditorFocus, setIsEditorFocus] = useState(false); // 不包含unfix段落

      const displayPids = controller.getPids(true);
      const hasUnfixParagraph =
        displayPids.filter(pid => !controller.getParagraph(pid).isUnfix).length > 0;
      const livingMode = controller.getLivingMode();
      const isEnabled = livingMode && hasUnfixParagraph;

      const isShowingLivingScrollToBottom = isEnabled && !isScrollEnd;

      const forceUpdate = useForceUpdate();

      const scrollToBottom = useCallback(() => {
        const containerElem = getContainerElem();
        if (!containerElem) {
          return;
        }
        containerElem.scrollTo({
          top: containerElem.scrollHeight,
        });
        setIsScrollEnd(true);
        setNeedScrollToEnd(true);
      }, []);

      const handleScrollToBottom = useCallback(
        (type: string) => {
          const { onEvent } = getHooks();
          type !== "livingIsUnfix" && onEvent && onEvent("clickScrollToBottom", {});
          scrollToBottom();
        },
        [scrollToBottom]
      );

      useEffect(() => {
        if (type === "needUnfixScroll" && !isEnabled) {
          timer = setInterval(() => {
            scrollToBottom();
          }, 300);
          return;
        }
        clearInterval(timer);
      }, [isEnabled]);

      useEffect(() => {
        if (!isEnabled) {
          return;
        }
        return controller.on("uglyUserManualAddParagraph", () => {
          setTimeout(scrollToBottom, 100);
        });
      }, [isEnabled, scrollToBottom]);

      const checkIsScrollEnd = useCallback(() => {
        const containerElem = getContainerElem();
        if (!containerElem) {
          return;
        }
        const buffer = 50;
        const _isScrollEnd =
          containerElem.scrollTop + containerElem.clientHeight >=
          containerElem.scrollHeight - buffer;
        setIsScrollEnd(_isScrollEnd);
        setNeedScrollToEnd(_isScrollEnd);
      }, []);

      // todo: 临时解决带unfix区focus时成段，导致无法scrollToBottom
      // 其中插入Dom和rectResize之间会有一次scroll事件发生，导致检查scrollToBottom为false
      // 其中editPidChange会被重新触发，导致检查对应上屏pid变为普通pid
      const { getJustFixPid, destroyGetJustFixPid } = useMemo(() => {
        return generateGetJustFixPid(controller);
      }, []);

      useEffect(() => {
        return destroyGetJustFixPid;
      }, [destroyGetJustFixPid]);

      const handleScroll = useCallback(() => {
        if (getJustFixPid()) {
          return;
        }
        checkIsScrollEnd();
      }, [checkIsScrollEnd, getJustFixPid]);

      useSyncEffect(() => {
        if (!isEnabled) {
          return;
        }
        return controller.on("searchReplaceChange", checkIsScrollEnd);
      }, [isEnabled, checkIsScrollEnd]);

      useSyncEffect(() => {
        return controller.on("livingModeChange", forceUpdate);
      }, [forceUpdate]);

      useSyncEffect(() => {
        if (!livingMode) {
          return;
        }
        return controller.on("paragraphListChange", forceUpdate);
      }, [livingMode, forceUpdate]);

      useSyncEffect(() => {
        if (!isEnabled) {
          return;
        }
        return controller.on("editorFocusChange", ({ pid }) => {
          let isFocus: boolean;

          if (getJustFixPid()) {
            return;
          }
          if (pid) {
            const paragraph = controller.getParagraph(pid);
            isFocus = !paragraph.isUnfix;
          } else {
            isFocus = false;
          }

          setIsEditorFocus(isFocus);
          checkIsScrollEnd();
        });
      }, [isEnabled, checkIsScrollEnd, getJustFixPid]);

      useSyncEffect(() => {
        if (!isEnabled) {
          return;
        }
        return tranController.on("contentRectChange", () => {
          if (needScrollToEnd && !isEditorFocus) {
            scrollToBottom();
          } else {
            checkIsScrollEnd();
          }
        });
      }, [isEnabled, needScrollToEnd, isEditorFocus, scrollToBottom, checkIsScrollEnd]);

      return {
        ...props,
        onScroll: useCallback(() => {
          if (scrollEaseOutAnimation.isAnimating) return;

          onScroll && onScroll();
          if (isEnabled) {
            handleScroll();
          }
        }, [isEnabled, onScroll, handleScroll]),
        isShowingLivingScrollToBottom,
        onScrollToBottom: handleScrollToBottom,
      };
    },
    extendByNothing: props => props,
  };
};
