import { Controller, PID, TextPolishMode, TranslateMode } from "@tingwu/core";
import { debounce, throttle } from "lodash-es";
import React, { useState, useCallback, useEffect, useRef, useMemo } from "react";
import { useRecoilValue } from "recoil";
import { isRenderUnfixParagraphState, isRoughlyTranscripingState } from "../model";
import useForceUpdate from "use-force-update";
import { TranscriptionController, useTranscriptionHooks } from "../controller";
import { calcParagraphHeight, getDirection } from "../utils";

const SPLIT_PADDING = 20;

export interface Size {
  width: number;
  height: number;
}

interface UseScrollViewParams {
  controller: Controller;
  transController: TranscriptionController;
  theme: any;
  rootClassName?: string;
}

const hi = (arg: any) => {
  return arg;
};

export const useScrollView = (params: UseScrollViewParams) => {
  const { controller, transController, theme, rootClassName } = params;

  const { playingWordController } = transController;
  const forceUpdate = useForceUpdate();

  const stateRef = useRef<{
    pidSizeMapping: Record<PID, Size>;
  }>({
    pidSizeMapping: {},
  });
  const [calcCount, setCalcCount] = useState(0);
  const livingMode = controller.getLivingMode();
  const readonly = controller.getReadonly();
  const [container, setContainer] = useState<HTMLElement>();
  const [documentElem, setDocumentElem] = useState<HTMLElement>();
  const [scrollTop, setScrollTop] = useState<number>(0);
  const [containerWidth, setContainerWidth] = useState(0);
  const containerWidthRef = useRef(containerWidth);
  containerWidthRef.current = containerWidth;

  const translateMode = controller.getTranslateMode();
  const textPolishMode = controller.getTextPolishMode(); // AI改写模式 备注：不和翻译同时存在

  // 展示翻译
  const showTranslate = [TranslateMode.OriginAndTranslate, TranslateMode.TranslateOnly].includes(
    translateMode
  );
  // 展示AI原文改写
  const showTextPolish = [
    TextPolishMode.OriginAndTextPolish,
    TextPolishMode.TextPolishOnly,
  ].includes(textPolishMode);
  // 展示原文
  const showOrigin =
    [TranslateMode.Disabled, TranslateMode.OriginAndTranslate].includes(translateMode) &&
    [TextPolishMode.Disabled, TextPolishMode.OriginAndTextPolish].includes(textPolishMode);

  const getBeforeRenderHeight = useCallback(() => {
    if (!container || !documentElem) {
      return 0;
    }

    const containerRect = container.getBoundingClientRect();
    const documentRect = documentElem.getBoundingClientRect();
    return documentRect.top - containerRect.top + container.scrollTop;
  }, [container, documentElem]);

  const beforeRenderHeight = getBeforeRenderHeight();

  useEffect(() => {
    const interval = setInterval(() => {
      if (beforeRenderHeight !== getBeforeRenderHeight()) {
        forceUpdate();
      }
    }, 200);

    return () => {
      clearInterval(interval);
    };
  }, [forceUpdate, getBeforeRenderHeight, beforeRenderHeight]);

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

  // AI 改写模式变更监听
  useEffect(() => {
    return controller.on("textPolishModeChange", () => {
      transController.emit("needRefresh", {});
    });
  }, [controller, forceUpdate, transController]);

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

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

  const { beforeCoverHeight } = useTranscriptionHooks(controller, ["beforeCoverHeight"]);

  useEffect(() => {
    setTimeout(() => {
      const { getTranscriptionContainer, getTranscriptionDocument } = transController.getHooks();
      const elem = getTranscriptionContainer ? getTranscriptionContainer() : undefined;
      const _documentElem = getTranscriptionDocument ? getTranscriptionDocument() : undefined;
      setContainer(elem);
      setDocumentElem(_documentElem);
    }, 200);
  }, [transController]);

  useEffect(() => {
    if (!container) {
      return;
    }
    const handleScroll = throttle(() => {
      setScrollTop(container.scrollTop);
    }, 100);
    container.addEventListener("scroll", handleScroll, false);
    return () => {
      container.removeEventListener("scroll", handleScroll, false);
    };
  }, [container]);

  const calcHeight = useCallback(
    (pid: PID) => {
      if (!containerWidth) {
        return 0;
      }

      const height = calcParagraphHeight({
        controller,
        transController,
        pid,
        theme,
        livingMode,
        readonly,
        showOrigin,
        showTranslate,
        showTextPolish,
        containerWidth,
      });

      stateRef.current.pidSizeMapping[pid] = {
        width: containerWidth,
        height,
      };
    },
    [
      containerWidth,
      controller,
      transController,
      theme,
      livingMode,
      readonly,
      showOrigin,
      showTranslate,
      showTextPolish,
    ]
  );

  const calcAll = useCallback(() => {
    setTimeout(() => {
      const pids = controller.getPids();
      pids.forEach(pid => {
        calcHeight(pid);
      });
      setCalcCount(prev => prev + 1);
    });
  }, [controller, calcHeight]);

  useEffect(() => {
    if (!container) {
      return;
    }

    const elem = document.querySelector(
      `${rootClassName ? `.${rootClassName} .transcriptionContent` : ".transcriptionContent"}`
    );
    if (elem) {
      const paddingLeft = parseInt(getComputedStyle(elem).paddingLeft, 10);
      const paddingRight = parseInt(getComputedStyle(elem).paddingRight, 10);
      setContainerWidth(elem.clientWidth - paddingLeft - paddingRight);
    }
    return transController.on(
      "contentRectChange",
      throttle(
        ({ rect }) => {
          if (containerWidthRef.current !== rect.width) {
            setContainerWidth(rect.width);
          }
        },
        16.7,
        { leading: false, trailing: true }
      )
    );
  }, [transController, rootClassName, container]);

  useEffect(() => {
    let timeout: ReturnType<typeof setTimeout>;
    return transController.on("contentRectChange", () => {
      controller.calcCaretStyles(false);
      controller.calcSelectionStyles();
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        controller.calcCaretStyles(false);
        controller.calcSelectionStyles();
      }, 100);
    });
  }, [transController, controller]);

  useEffect(() => {
    return controller.on("modelValueChange", () => {
      calcAll();
    });
  }, [controller, calcAll]);

  useEffect(() => {
    calcAll();
  }, [calcAll]);

  useEffect(() => {
    return controller.on("paragraphChangeSpecific", event => {
      const { addParagraphPids, removeParagraphPids, updateParagraphPids } = event;

      addParagraphPids.forEach(calcHeight);
      updateParagraphPids.forEach(calcHeight);
      removeParagraphPids.forEach(pid => {
        delete stateRef.current.pidSizeMapping[pid];
      });

      setCalcCount(prev => prev + 1);
    });
  }, [controller, calcHeight]);

  useEffect(() => {
    return controller.on("fixParagraphTranslateChange", ({ pid }) => {
      calcHeight(pid);
      setCalcCount(prev => prev + 1);
    });
  }, [controller, calcHeight]);

  useEffect(() => {
    return controller.on("fixParagraphTextPolishChange", ({ pid }) => {
      calcHeight(pid);
      setCalcCount(prev => prev + 1);
    });
  }, [controller, calcHeight]);

  const isRoughlyTranscriping = useRecoilValue(isRoughlyTranscripingState);
  const isRenderUnfixParagraph = useRecoilValue(isRenderUnfixParagraphState);
  const unfixPid = livingMode ? controller.getUnfixPid() : undefined;
  const filteredPidsRef = useRef(controller.getPids(true));
  filteredPidsRef.current = controller.getPids(true);
  const displayPids = isRoughlyTranscriping
    ? filteredPidsRef.current.filter(pid => pid !== unfixPid)
    : filteredPidsRef.current;
  const [playingVoicePid, setPlayingVoicePid] = useState<PID>();

  useEffect(() => {
    return transController.on("needRefresh", () => {
      setCalcCount(prev => prev + 1);
    });
  }, [transController]);

  const [pidsWithStyle, containerHeight] = useMemo(() => {
    hi(calcCount);
    const { pidSizeMapping } = stateRef.current;
    let top = 0;
    const result: Array<{
      pid: PID;
      style: React.CSSProperties & {
        top: number;
        width: number;
        height: number;
      };
      getBeforeContent: () => React.ReactNode;
      beforeContentHeight: number;
    }> = [];

    displayPids.some(pid => {
      const node = pidSizeMapping[pid];
      if (!node) {
        return false;
      }

      const { width, height } = node;

      if (height === undefined) {
        return true;
      }

      let renderHeight = height;
      let getBeforeContent: () => React.ReactNode = () => null;

      const paragraphTopParams = {
        pid,
        width,
      };

      let beforeContentHeight = 0;
      if (transController.isHasParagraphTop(paragraphTopParams)) {
        beforeContentHeight = transController.getParagraphTopHeight(paragraphTopParams);
        renderHeight += beforeContentHeight;
        getBeforeContent = () => (
          <div style={{ width, height: beforeContentHeight }}>
            {transController.renderParagraphTop(paragraphTopParams)}
          </div>
        );

        // 如果不是第一段，且有额外渲染，则抵消top的边距
        if (top !== 0) {
          top -= SPLIT_PADDING;
        }
      }

      result.push({
        pid,
        style: {
          top,
          width,
          height: renderHeight,
        },
        getBeforeContent,
        beforeContentHeight,
      });

      top += renderHeight + SPLIT_PADDING;
      return false;
    });

    if (!isRenderUnfixParagraph) {
      top -= SPLIT_PADDING;
    }

    return [result, Math.max(0, top)];
  }, [
    isRenderUnfixParagraph,
    displayPids,
    calcCount,
    isRoughlyTranscriping,
    unfixPid,
    transController,
  ]);

  const pidsWithStyleRef = useRef(pidsWithStyle);

  useEffect(() => {
    pidsWithStyleRef.current = pidsWithStyle;
  }, [pidsWithStyle]);

  const playingVoicePara = useMemo(() => {
    return pidsWithStyle.find(({ pid }) => pid === playingVoicePid);
  }, [pidsWithStyle, playingVoicePid]);

  const scrollViewPids = useMemo(() => {
    if (scrollTop == null || !container) {
      return [];
    }
    const scrollViewHeight = container.clientHeight;
    const startAt = scrollTop - scrollViewHeight * 2 - beforeRenderHeight;
    const endAt = scrollTop + scrollViewHeight * 3 - beforeRenderHeight;

    const pids = pidsWithStyle.filter(({ style }) => {
      // 计算两个区间[a,b] [c,d]是否有重叠, 通过判断min(b,d) > max(a,c)来判断是否重叠
      return Math.min(style.top + style.height, endAt) > Math.max(style.top, startAt);
    });

    if (playingVoicePid && !pids.find(({ pid }) => pid === playingVoicePid)) {
      if (playingVoicePara) {
        pids.push(playingVoicePara);
      }
    }
    return pids;
  }, [pidsWithStyle, scrollTop, container, playingVoicePid, playingVoicePara, beforeRenderHeight]);

  useEffect(() => {
    return controller.on("playingVoiceWordChange", ({ pid, word }) => {
      if (!word) {
        return;
      }
      setPlayingVoicePid(pid);
    });
  }, [controller]);

  const debounceCalcPlayingWordVisible = debounce(
    (
      playingParagraphTop: number,
      playingParagraphHeight: number,
      newScrollTop: number,
      scrollViewHeight: number
    ) => {
      return playingWordController.calcPlayingWordVisible(
        playingParagraphTop,
        playingParagraphHeight,
        newScrollTop,
        scrollViewHeight
      );
    },
    300
  );

  useEffect(() => {
    if (!playingVoicePara || !container) {
      playingWordController.disablePlayingWord();
      return;
    }

    const beforeHeight = playingVoicePara.beforeContentHeight;
    debounceCalcPlayingWordVisible(
      playingVoicePara.style.top + beforeRenderHeight + beforeHeight,
      playingVoicePara.style.height - beforeHeight,
      scrollTop,
      container.clientHeight
    );
  }, [
    playingWordController,
    playingVoicePara,
    scrollTop,
    container,
    beforeRenderHeight,
    debounceCalcPlayingWordVisible,
  ]);

  useEffect(() => {
    if (!container) {
      return;
    }
    return playingWordController.on(
      "paragraphShowUp",
      ({ pid: _pid, force, seekWithExtContent }) => {
        const para = pidsWithStyleRef.current.find(({ pid }) => pid === _pid);
        if (para) {
          let paddingBottom = 0;
          if (livingMode) {
            const unfixPara = container.querySelector('[data-paragraph-is-unfix="true"]');
            if (unfixPara && controller.getUnfixPid() !== _pid) {
              paddingBottom = Math.min(
                unfixPara.getBoundingClientRect().height,
                container.clientHeight
              );
            }
          }
          let top = para.style.top + getBeforeRenderHeight();
          const bottom = top + para.style.height;
          const scrollBottom = container.scrollTop + container.clientHeight - paddingBottom;

          if (!seekWithExtContent) {
            const extContentHeight = transController.getParagraphTopHeight({
              pid: _pid,
              width: containerWidth,
            });

            if (extContentHeight) {
              top += extContentHeight;
            }
          }

          if (beforeCoverHeight) {
            top -= beforeCoverHeight;
          }

          const direction = getDirection(top, bottom, container.scrollTop, scrollBottom);

          if (force || direction) {
            container.scrollTo({
              top: top - controller.getReviewHeight(),
            });
          }
        }
      }
    );
  }, [
    playingWordController,
    pidsWithStyleRef,
    container,
    livingMode,
    controller,
    transController,
    containerWidth,
    beforeCoverHeight,
    getBeforeRenderHeight,
  ]);

  const isFirstParagraphBeforeContent =
    pidsWithStyle.length > 0 ? pidsWithStyle[0].beforeContentHeight > 0 : false;

  return {
    pidsWithStyle,
    scrollViewPids,
    containerHeight,
    isFirstParagraphBeforeContent,
  };
};
