import React, { Dispatch, ReactElement, SetStateAction, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
import {
  noop,
  get,
  addEventListenerWrapper,
  dataURLToBlob,
  nextTick,
  toRealNumber,
  getTransforms,
  formatDurationTime
} from '@zhst/func';
import Align from 'rc-align';
import { Rect, IScreenshotButtonProp, AlignType } from '@zhst/types'
import { useLatest, useUpdateEffect, useFullscreen, useUnmount } from '@zhst/hooks';
import classNames from 'classnames';
import download from 'downloadjs';
import { Button, message } from '..';
import Icon from '../iconfont';
import {
  Cropper,
  EVENT_CROP_START,
  EVENT_CROP_END,
} from '../ImageEditor';
import FlvPlayer, { FLV_EVENT } from './components/FlvPlayer';
import Range from './components/Progress';
import Loading from './components/Loading';
import { CROP_TYPE } from '../utils/constants';
import { getShowStatus } from './videoPlayerHelper'
import './index.less'

const componentName = `zhst-image__video-view`;

export interface VideoViewProps {
  /* 播放地址 */
  url: string;
  /* 播放总时间 */
  maxDuration?: number;
  /* 截图渲染 */
  screenshotButtonAlign?: AlignType;
  screenshotButtonRender?: (screenshotButtonProp: IScreenshotButtonProp) => ReactElement;
  /* 默认截图框 */
  defautlNormalizationRect?: Rect;
  /* 截图回调 */
  onCropChange?: (showCrop: boolean, normalizationRect: null | Rect) => void;
}

export interface VideoViewRef {
  /* 当前图片模式 */
  cropAble: boolean;
  setShowCrop: Dispatch<SetStateAction<boolean>>;
  downloadVideoframe: () => void;
}

const VideoPlayer = forwardRef<VideoViewRef, VideoViewProps>((props, ref) => {
  const {
    url,
    maxDuration = 20,
    screenshotButtonAlign = {
      points: ['bl', 'br'],
      offset: [6, 0],
      overflow: {
        adjustX: true,
        adjustY: true,
      },
    },
    screenshotButtonRender = () => <div style={{ color: '#fff', width: '80px', top: 0 }}>回调DOM</div>,
    onCropChange,
    defautlNormalizationRect: defaultNormalizationRect,
  } = props;
  // ========================== 播放 =========================
  //实例参数
  const containerRef: any = useRef(null); //容器ref
  const videoRef: any = useRef(null); //video 标签dom
  const videoInsRef: any = useRef(null); //flv 实例
  const [playSeq, setPlaySeq] = useState(0); // 通过重置playid使FLV组件重新渲染
  const videoRemoveListener = useRef(noop); //移除dom监听的中间函数
  const loadingTimeRef = useRef<number | null>(0); //最后一次加载时间
  const delayLoadingTimer: any = useRef(null); //算loading的定时器
  //状态参数
  const [isReady, setIsReady] = useState(false); //
  const [isPlay, setIsPlay] = useState(false); //当前是否播放

  const [isEnd, setIsEnd] = useState(false); //是否播放结束
  const [isError, setIsError] = useState(false); //播放出错
  const [isVideoLoadFinished, setIsVideoLoadFinish] = useState(false); //是否缓存加载完成
  const [playTime, setPlayTime] = useState(0); //当前播放时间
  const [isLoadingVideo, setIsLoadingVideo] = useState(true); //是否加载中
  const [isDelayLoading, setIsDelayLoading] = useState(false); //显示的转圈loading 延迟0.2s显示
  //设置延迟转圈圈
  const latestIsLoadingVideo = useLatest(isLoadingVideo);
  const setIsLoadingVideoWrapper = (isLoading: boolean) => {
    setIsLoadingVideo((preLoading) => {
      if (!preLoading && isLoading) {
        loadingTimeRef.current = new Date().getTime();
      }
      if (!isLoading) {
        loadingTimeRef.current = null;
      }
      //延迟0。2s相关
      if (!isLoading) {
        setIsDelayLoading(false);
      }
      if (!delayLoadingTimer.current && preLoading) {
        delayLoadingTimer.current = setTimeout(() => {
          if (latestIsLoadingVideo.current) {
            //0.2s后才显示
            setIsDelayLoading(true);
          }
          delayLoadingTimer.current = null;
        }, 200);
      }
      return isLoading;
    });
  };
  // 初始化loading 30s 直接显示错误
  // TODO :逻辑忘记了 不应该是每次init player吗？
  useEffect(() => {
    let timer = setInterval(() => {
      if (loadingTimeRef.current) {
        if (new Date().getTime() - loadingTimeRef.current > 1000 * 30) {
          checkIsErr()
        }
      }
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);

  //结束的时候暂停 保证不播了
  useUpdateEffect(() => {
    if (isEnd) {
      videoInsRef?.current?.pause?.();
    }
  }, [isEnd]);

  // 捕捉视频播放报错
  const checkIsErr = () => {
    setIsError(true)
    try {
      videoInsRef?.current?.destroy?.();
    } catch (error) {
      console.error(error);
    }
  }

  // 初始化
  const latestMaxDuration = useLatest(maxDuration);
  const initPlayer = useCallback((ins: any, dom: any) => {
    videoRef.current = dom;
    videoInsRef.current = ins;
    const maxDuration = latestMaxDuration.current || 0;
    //监听播放事件
    let video = dom;
    let errorLister = (e: any) => {
      checkIsErr();
      console.error('视频出错了', e, video.currentTime);
    };
    let waitingListener = () => {
      setIsLoadingVideoWrapper(true);
      // console.debug('视频加载等待', e, video.currentTime);
    };
    let playingListener = () => {
      setIsLoadingVideoWrapper(false);
      setIsError(false)
      // console.debug('视频从等待中播放', e, video.currentTime);
    };
    let playLister = () => {
      setIsPlay(true);
      setIsError(false)
      // console.debug('提示该视频正在播放中', e, video.currentTime);
    };
    let pauseListener = () => {
      setIsPlay(false);
      // console.debug('暂停播放', e, video.currentTime);
    };
    let endedListner = () => {
      setIsEnd(true);
      setIsVideoLoadFinish(true);
      // console.debug('视频播放完了', e, video.currentTime);
    };
    let timeupdateListner = () => {
      let nowTime = video.currentTime;
      if (nowTime >= maxDuration) {
        setIsEnd(true);
        setIsVideoLoadFinish(true);
      }
      setPlayTime(nowTime);
    };
    // see https://github.com/bilibili/flv.js/issues/337
    let windowErrorHandle = (errorEvent: any) => {
      try {
        if (
          errorEvent['message'] ==
          "Uncaught TypeError: Cannot read property 'flushStashedSamples' of null"
        ) {
          checkIsErr();
          console.error('视频出错了 window监听', errorEvent);
        }
      } catch (error) {
        console.error(error);
      }
    };
    video.addEventListener('error', errorLister);
    video.addEventListener('waiting', waitingListener);
    video.addEventListener('playing', playingListener);
    video.addEventListener('play', playLister);
    video.addEventListener('pause', pauseListener);
    video.addEventListener('ended', endedListner);
    video.addEventListener('timeupdate', timeupdateListner);
    window.addEventListener('error', windowErrorHandle);

    videoRemoveListener.current = () => {
      video.removeEventListener('error', errorLister);
      video.removeEventListener('waiting', waitingListener);
      video.removeEventListener('playing', playingListener);
      video.removeEventListener('play', playLister);
      video.removeEventListener('pause', pauseListener);
      video.removeEventListener('ended', endedListner);
      video.removeEventListener('timeupdate', timeupdateListner);
      window.removeEventListener('error', windowErrorHandle);
    };

    videoInsRef?.current.on(FLV_EVENT.ERROR, (type: any, errDetail: any, info: any) => {
      checkIsErr();
      console.error('videoInsRef 错误', type, errDetail, info, video.currentTime);
    });
    let playPromise = videoInsRef?.current.play();
    //先ready 遮挡会导致播放失败
    setIsReady(true);
    playPromise
      .then(() => {
        setIsReady(true);
      })
      .catch((...arg: any) => {
        try {
        } catch (error) {}
        // setIsError(true);
        console.error('playPromise视频出错了', arg);
      });
  }, []);

  useUnmount(() => {
    try {
      videoRemoveListener.current();
    } catch (e) {
      console.error(e);
    }
  });

  const reload = async () => {
    if (videoInsRef.current) {
      let oldTime = videoInsRef.current.currentTime;
      videoInsRef.current.currentTime = 0;

      //如果修改时间不成功，则走重新加载的逻辑
      if (oldTime === videoInsRef.current.currentTime) {
        //重置状态
        setIsReady(false);
        setIsPlay(false);
        setIsLoadingVideoWrapper(false);
        setIsReady(false);
        setIsEnd(false);
        setIsVideoLoadFinish(false);
        setPlayTime(0);

        //清楚dom事件监听
        try {
          videoRemoveListener.current();
        } catch (error) {
          console.error(error);
        }
        setPlaySeq((pre) => pre + 1);
        return;
      }
      videoInsRef.current.play();
    }

    setPlayTime(0);
    setIsEnd(false);
  };
  const seek = (v: number) => {
    if (videoInsRef.current && isVideoLoadFinished) {
      setPlayTime(parseFloat(v as any));
      videoInsRef.current.currentTime = parseFloat(v as any);
    } else {
      message.warning('待视频加载完，才可操作进度条')
    }
  };
  // ========================== 视频opt bar =========================
  const [isFullscreen, { toggleFullscreen }] = useFullscreen(containerRef, {
    pageFullscreen: true,
  });
  const showMaxDuration = !!maxDuration
    ? maxDuration
    : toRealNumber(get(videoRef, 'current.duration', 0));
  const showSlider = videoInsRef.current && isVideoLoadFinished;
  const showStatus: any = getShowStatus(isDelayLoading, isEnd, isError);

  // ========================== 截图 =========================
  const corpContainerRef: any = useRef();
  const cropInsRef: any = useRef(null);
  const [showCrop, setShowCrop] = useState<boolean>(false);

  //回显默认框选
  const isFirstFlagRef = useRef<boolean>(true);
  useEffect(() => {
    const isFirst = isFirstFlagRef.current;
    if (!isLoadingVideo && isReady && isFirst && defaultNormalizationRect && !showStatus) {
      nextTick(() => {
        setShowCrop(true);
      });
    }
  }, [isLoadingVideo, showStatus]);

  //定位按钮相关参数
  const alginContainerRef: any = useRef(null);
  const alignRef: any = useRef(null);
  const [cropRect, setCropRect] = useState<Rect| null>(null);

  useEffect(() => {
    showCrop ? videoInsRef?.current?.pause() : videoInsRef?.current?.play();
  }, [showCrop]);

  useEffect(() => {
    let handlerCropStart: { remove: () => void; };
    let handlerCropEnd: { remove: () => void; };
    setCropRect(null);
    if (!isReady) return;
    if (showCrop) {
      handlerCropStart = addEventListenerWrapper(corpContainerRef.current, EVENT_CROP_START, () => {
        setCropRect(null);
      });
      handlerCropEnd = addEventListenerWrapper(corpContainerRef.current, EVENT_CROP_END, (event: { detail: any; }) => {
        const data = event.detail;
        setCropRect({
          x: data.left,
          y: data.top,
          w: data.width,
          h: data.height,
        });
        alignRef?.current?.forceAlign?.();
      });
      let video: any = videoRef.current;
      //计算 limitcroppbox
      let scale = Math.min(
        video.offsetWidth / video.videoWidth,
        video.offsetHeight / video.videoHeight
      );
      let finalVideoWidth = video.videoWidth * scale;
      let finalVideoHeight = video.videoHeight * scale;
      let cropBoxLimited = {
        width: finalVideoWidth,
        height: finalVideoHeight,
        top: (video.offsetHeight - finalVideoHeight) / 2,
        left: (video.offsetWidth - finalVideoWidth) / 2,
      };
      //获取视频图片
      let canvas = document.createElement('canvas');
      canvas.width = video.offsetWidth;
      canvas.height = video.offsetHeight;
      canvas.style.display = 'none';
      document.body.appendChild(canvas);
      let ctx = canvas.getContext('2d');
      ctx?.drawImage(
        video,
        (video.offsetWidth - finalVideoWidth) / 2,
        (video.offsetHeight - finalVideoHeight) / 2,
        finalVideoWidth,
        finalVideoHeight
      );
      let imageData = canvas.toDataURL('image/png');
      canvas.parentNode?.removeChild(canvas);
      //回显编辑框
      const isFirst = isFirstFlagRef.current;
      let initialCropBoxData = null;
      if (isFirst && defaultNormalizationRect) {
        initialCropBoxData = {
          left: defaultNormalizationRect.x * finalVideoWidth + cropBoxLimited.left,
          top: defaultNormalizationRect.y * finalVideoHeight + cropBoxLimited.top,
          width: defaultNormalizationRect.w * finalVideoWidth,
          height: defaultNormalizationRect.h * finalVideoHeight,
        };
      }
      isFirstFlagRef.current = false;

      cropInsRef.current = new Cropper(corpContainerRef.current, {
        showMask: true,
        cropBoxLimited,
        img: imageData,
        initialCropBoxData,
      });
    }
    return () => {
      handlerCropStart?.remove();
      handlerCropEnd?.remove();
      cropInsRef?.current?.destroy?.();
      cropInsRef.current = null;
    };
  }, [showCrop, isReady]);

  const latestCropRect = useLatest(cropRect);

  const getCropInfo = async () => {
    const cropRect = latestCropRect.current as any;
    let video: any = videoRef.current;
    if (!video) return
    let rectList = [];
    let extendRectList = [];
    let selectIndex = 0;
    //获取视频图片的url
    let scale = Math.min(
      video.offsetWidth / video.videoWidth,
      video.offsetHeight / video.videoHeight
    );
    let finalVideoWidth = video.videoWidth * scale;
    let finalVideoHeight = video.videoHeight * scale;
    let canvas = document.createElement('canvas');
    canvas.width = finalVideoWidth;
    canvas.height = finalVideoHeight;
    canvas.style.display = 'none';
    document.body.appendChild(canvas);
    let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
    ctx.drawImage(
      video,
      0,
      0,
      finalVideoWidth,
      finalVideoHeight
    );
    let base64 = canvas.toDataURL('image/jpeg');
    const blobData = dataURLToBlob(base64);
    canvas.parentNode?.removeChild(canvas);
    const file = new window.File([blobData], `${new Date().getTime()}`);
    let newRect = {
      w: cropRect.w / finalVideoWidth,
      h: cropRect.h / finalVideoHeight,
      x: (cropRect.x - (video.offsetWidth - finalVideoWidth) / 2) / finalVideoWidth,
      y: (cropRect.y - (video.offsetHeight - finalVideoHeight) / 2) / finalVideoHeight
    };
    rectList.push(newRect);
    extendRectList.push(newRect);
    //扩展框获取imgkey
    extendRectList.forEach(async (rect, index) => {
      extendRectList[index] = {
        ...rect,
      };
    })
    return {
      rectList,
      extendRectList,
      selectIndex,
      file
    };
  };

  //回调
  useEffect(() => {
    //计算归一化crop rect
    let normalizationRect = null;
    if (showCrop && cropRect) {
      let video: any = videoRef.current;
      let scale = Math.min(
        video.offsetWidth / video.videoWidth,
        video.offsetHeight / video.videoHeight
      );
      let finalVideoWidth = video.videoWidth * scale;
      let finalVideoHeight = video.videoHeight * scale;
      let cropBoxLimited = {
        width: finalVideoWidth,
        height: finalVideoHeight,
        top: (video.offsetHeight - finalVideoHeight) / 2,
        left: (video.offsetWidth - finalVideoWidth) / 2,
      };
      normalizationRect = {
        x: (cropRect.x - cropBoxLimited.left) / cropBoxLimited.width,
        y: (cropRect.y - cropBoxLimited.top) / cropBoxLimited.height,
        w: cropRect.w / cropBoxLimited.width,
        h: cropRect.h / cropBoxLimited.height,
      };
    }

    onCropChange?.(showCrop, normalizationRect);
  }, [showCrop, cropRect]);

  // ========================== 截帧 =========================
  const downloadVideoframe = useCallback(async () => {
    try {
      videoInsRef?.current?.pause?.();
      let video: any = videoRef.current;
      var canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d')
      let base64;
      //当视频处于还未加载出来时，截屏为黑色图片
      if (video.readyState === 0) {
        ctx?.clearRect(0, 0, canvas.width, canvas.height);
        canvas.width = video.offsetWidth;
        canvas.height = video.offsetHeight;
        // @ts-ignore
        ctx.fillStyle = 'black';
        ctx?.fillRect(0, 0, canvas.width, canvas.height);
        base64 = canvas.toDataURL();
      } else {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        ctx?.drawImage(video, 0, 0, canvas.width, canvas.height);
        base64 = canvas.toDataURL('image/png');
      }
      download(base64);
    } catch (error) {
      console.error(error);
    }
  }, []);

  // ============================== 暴露出去的方法 ===============================
  const latestIsReady = useLatest(isReady);
  const cropAble = !showStatus && isReady;
  useImperativeHandle(ref, () => ({
    cropAble,
    setShowCrop: (dispatch) => {
      const isReady = latestIsReady.current;
      if (!isReady) return;
      setShowCrop(dispatch);
    },
    downloadVideoframe,
  }));

  return (
    <div className={classNames(`${componentName}`)} ref={containerRef}>
      {url && (
        <>
          <FlvPlayer
            playId={playSeq}
            autoPlay={true}
            className={classNames(`${componentName}-flv`)}
            type={url.startsWith('http') ? 'mp4' : 'flv'}
            url={url}
            config={{
              enableStashBuffer: true,
              stashInitialSize: 1024 * 700,
              isLive: true,
              hasAudio: false,
              hasVideo: true,
            }}
            onCreat={initPlayer}
          />
          {/* //截图 */}
          <div
            className={classNames(`${componentName}-crop-container`)}
            ref={corpContainerRef}
            style={{
              display: isFullscreen ? 'none' : 'block',
            }}
          >
            {/* <div ref={corpRef}></div> */}
          </div>
          {showCrop && cropRect && (
            <>
              <div
                ref={alginContainerRef}
                className={classNames(`${componentName}-align`)}
                style={Object.assign(
                  {
                    width: cropRect.w,
                    height: cropRect.h,
                  },
                  getTransforms({
                    translateX: cropRect.x,
                    translateY: cropRect.y,
                  })
                )}
              ></div>
              <Align
                ref={alignRef}
                monitorWindowResize
                align={screenshotButtonAlign}
                target={function () {
                  return alginContainerRef.current;
                }}
              >
                {screenshotButtonRender({
                  model: 'IMAGE',
                  getCropInfo,
                  setShowCrop,
                  cropType: CROP_TYPE['CUSTOM'],
                })}
              </Align>
            </>
          )}
          {/* 视频进度条 */}
          {!showCrop && (
            <div className={`${componentName}-opt`}>
              <div>
                <Button
                  type="text"
                  onClick={() => {
                    if (!isPlay) {
                      //播放中暂停
                      videoInsRef?.current?.play();
                      setShowCrop(false);
                    } else {
                      videoInsRef?.current?.pause();
                    }
                  }}
                >
                  <Icon
                    styles={{
                      color: '#fff',
                      display: 'flex',
                    }}
                    color="#1890ff"
                    icon={!isPlay ? 'icon-shipinbofang' : 'icon-shipinzanting'}
                  />
                </Button>
              </div>
              <div
                className={`${componentName}-opt-range`}
                onClick={(e) => {
                  e.stopPropagation();
                }}
              >
                <Range
                  value={playTime}
                  min={0}
                  max={showMaxDuration}
                  showSlider={showSlider}
                  onChange={seek}
                />
                <div>
                  {/* TODO: 删除扩展方法format */}
                  {formatDurationTime(playTime)}/{formatDurationTime(showMaxDuration)}
                </div>
              </div>
              <div>
                <Button
                  type="text"
                  onClick={(e) => {
                    e.stopPropagation();
                    toggleFullscreen();
                  }}
                >
                  <Icon
                    styles={{
                      color: '#fff',
                      display: 'flex',
                    }}
                    size={18}
                    icon={isFullscreen ? 'icon-cancle_fullscreen' : 'icon-fullscreen'}
                  />
                </Button>
              </div>
            </div>
          )}
          {/* mask */}
          {!!showStatus && (
            <Loading  status={showStatus} reload={() => reload()} />
          )}
        </>
      )}
    </div>
  );
});

export default VideoPlayer;
