import * as React from "react";
import { useOuterClick } from "./utils";
import { VisualState } from "./types";
import useKBar from "./useKBar";

interface KBarAnimatorProps {
  style?: React.CSSProperties;
  className?: string;
}

const appearanceAnimationKeyframes = [
  {
    opacity: 0,
    transform: "scale(.99)",
  },
  { opacity: 1, transform: "scale(1.01)" },
  { opacity: 1, transform: "scale(1)" },
];

const bumpAnimationKeyframes = [
  {
    transform: "scale(1)",
  },
  {
    transform: "scale(.98)",
  },
  {
    transform: "scale(1)",
  },
];

export const KBarAnimator: React.FC<KBarAnimatorProps> = ({
  children,
  style,
  className,
}) => {
  const { visualState, currentRootActionId } = useKBar((state) => ({
    visualState: state.visualState,
    currentRootActionId: state.currentRootActionId,
  }));

  const outerRef = React.useRef<HTMLDivElement>(null);
  const innerRef = React.useRef<HTMLDivElement>(null);

  const { options, query } = useKBar();

  const enterMs = options?.animations?.enterMs || 0;
  const exitMs = options?.animations?.exitMs || 0;

  // Show/hide animation
  React.useEffect(() => {
    if (visualState === VisualState.showing) {
      return;
    }

    const duration = visualState === VisualState.animatingIn ? enterMs : exitMs;

    const element = outerRef.current;

    element?.animate(appearanceAnimationKeyframes, {
      duration,
      easing:
        // TODO: expose easing in options
        visualState === VisualState.animatingOut ? "ease-in" : "ease-out",
      direction:
        visualState === VisualState.animatingOut ? "reverse" : "normal",
      fill: "forwards",
    });
  }, [options, visualState, enterMs, exitMs]);

  // Height animation
  const previousHeight = React.useRef<number>();
  React.useEffect(() => {
    // Only animate if we're actually showing
    if (visualState === VisualState.showing) {
      const outer = outerRef.current;
      const inner = innerRef.current;

      if (!outer || !inner) {
        return;
      }

      const ro = new ResizeObserver((entries) => {
        for (let entry of entries) {
          const cr = entry.contentRect;

          if (!previousHeight.current) {
            previousHeight.current = cr.height;
          }

          outer.animate(
            [
              {
                height: `${previousHeight.current}px`,
              },
              {
                height: `${cr.height}px`,
              },
            ],
            {
              duration: enterMs / 2,
              // TODO: expose configs here
              easing: "ease-out",
              fill: "forwards",
            }
          );
          previousHeight.current = cr.height;
        }
      });

      ro.observe(inner);
      return () => {
        ro.unobserve(inner);
      };
    }
  }, [visualState, options, enterMs, exitMs]);

  // Bump animation between nested actions
  const firstRender = React.useRef(true);
  React.useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false;
      return;
    }
    const element = outerRef.current;
    if (element) {
      element.animate(bumpAnimationKeyframes, {
        duration: enterMs,
        easing: "ease-out",
      });
    }
  }, [currentRootActionId, enterMs]);

  useOuterClick(outerRef, () => {
    query.setVisualState(VisualState.animatingOut);
  });

  return (
    <div
      ref={outerRef}
      style={{
        ...appearanceAnimationKeyframes[0],
        ...style,
        pointerEvents: "auto",
      }}
      className={className}
    >
      <div ref={innerRef}>{children}</div>
    </div>
  );
};
