import React, {
  useRef,
  useState,
  useEffect,
  useMemo,
  forwardRef,
  useImperativeHandle,
  useCallback,
} from "react";
import { AffixProps } from "./AffixProps";
import { getTarget, getRect, throttle, getFixed } from "./util";
import { addListener, removeListener } from "./AffixManager";
import { uuid } from "../_util/uuid";
import { useIsomorphicLayoutEffect } from "../_util/use-isomorphic-layout-effect";
import { useUnmounted } from "../_util/use-unmounted";

export interface AffixHandles {
  update: () => void;
}

export const Affix = forwardRef(function Affix(
  {
    offsetTop,
    offsetBottom,
    target = window,
    children,
    style,
    className,
  }: AffixProps,
  ref: React.Ref<AffixHandles>
) {
  // 默认吸顶
  if (typeof offsetTop === "undefined" && typeof offsetBottom === "undefined") {
    offsetTop = 0; // eslint-disable-line no-param-reassign
  }
  const affixRef = useRef<HTMLDivElement>(null);
  const placeholderRef = useRef<HTMLDivElement>(null);
  const [needUpdate, setNeedUpdate] = useState<boolean>(false);
  const [affixStyle, setAffixStyle] = useState<React.CSSProperties>(undefined);
  const [placeholderStyle, setPlaceholderStyle] = useState<React.CSSProperties>(
    undefined
  );
  const unmountedRef = useUnmounted();

  const measure = useCallback(() => {
    const placeholder = placeholderRef.current;
    if (!needUpdate || !placeholder) {
      return;
    }
    const placeholderRect = getRect(placeholder);
    const sizeStyle = {
      width: placeholderRect.width,
      height: placeholderRect.height,
    };

    setPlaceholderStyle(sizeStyle);

    if (typeof offsetTop !== "undefined") {
      const top = getFixed(target, placeholder, offsetTop);
      setAffixStyle(
        typeof top !== "undefined"
          ? Object.assign(
              { position: "fixed", zIndex: 10, top } as React.CSSProperties,
              sizeStyle
            )
          : undefined
      );
    } else {
      const bottom = getFixed(target, placeholder, offsetBottom, false);
      setAffixStyle(
        typeof bottom !== "undefined"
          ? Object.assign(
              { position: "fixed", zIndex: 10, bottom } as React.CSSProperties,
              sizeStyle
            )
          : undefined
      );
    }
    setNeedUpdate(false);
  }, [needUpdate, offsetBottom, offsetTop, target]);

  const update = useMemo(
    () =>
      throttle(() => {
        if (!unmountedRef.current) {
          setAffixStyle(undefined);
          setPlaceholderStyle(undefined);
          setNeedUpdate(true);
        }
      }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    []
  );

  useIsomorphicLayoutEffect(() => {
    measure();
  }, [measure, needUpdate]);

  useImperativeHandle(ref, () => ({
    update,
  }));

  useEffect(() => {
    update();
    const id = uuid();
    const targetElement = getTarget(target);
    addListener(targetElement, id, update);
    return () => removeListener(targetElement, id);
  }, [offsetBottom, offsetTop, target, update]);

  return (
    <div ref={placeholderRef} className={className} style={style}>
      {affixStyle && <div style={placeholderStyle} />}
      <div ref={affixRef} style={affixStyle}>
        {children}
      </div>
    </div>
  );
});

Affix.displayName = "Affix";
