import { forwardRef, useCallback, useEffect } from "react";
import { useInView } from "react-intersection-observer";
import clsx from "clsx";
import { m } from "framer-motion";
import { type MDXRemoteSerializeResult } from "next-mdx-remote";

import MDXContent from "@/components/content/mdx";
import SplitText from "@/components/split-text";
import {
  fadeUpVariant,
  lightSpring,
  staggeredSlideUpVariant,
} from "@/helpers/animation";
import { useForwardedRef, useLayoutEffect, useResizeObserver } from "@/hooks";
import { useStore } from "@/store";
import { Theme } from "@/types";

import styles from "./styles.module.scss";

export interface BaseSectionProps {
  id: string;
  index?: number;
  title?: string;
  theme?: Theme;
  mdxSource?: MDXRemoteSerializeResult;
  variant?: "standard" | "full";
}

interface SectionProps extends BaseSectionProps {
  triggerOnce?: boolean;
  threshold?: number;
  onEnter?: () => void;
  onExit?: () => void;
  children?: React.ReactNode;
  className?: string;
  style?: React.CSSProperties;
}

const Section = forwardRef<HTMLElement, SectionProps>(
  (
    {
      id,
      index = 0,
      title,
      theme: sectionTheme = Theme.Dark,
      mdxSource,
      variant = "standard",
      triggerOnce = false,
      threshold = 0.15,
      onEnter,
      onExit,
      children,
      className,
      ...props
    },
    ref,
  ) => {
    const innerRef = useForwardedRef<HTMLElement>(ref);
    const sectionThemesMap = useStore.use.sectionThemes();
    const setCurrentSectionId = useStore.use.setCurrentSectionId();
    const setCurrentTheme = useStore.use.setCurrentTheme();
    const updateSectionThemes = useStore.use.updateSectionThemes();
    const updateSectionRefs = useStore.use.updateSectionRefs();
    const updateSectionHeights = useStore.use.updateSectionHeights();
    const { ref: inViewRef, inView } = useInView({
      rootMargin: "-50% 0px -50% 0px",
      onChange: (inView) => {
        if (inView && typeof onEnter === "function") {
          onEnter();
        } else if (!inView && typeof onExit === "function") {
          onExit();
        }
      },
    });
    const { ref: animateInViewRef, inView: animateInView } = useInView({
      triggerOnce,
      threshold,
    });

    const { height = 0 } = useResizeObserver<HTMLElement>({
      ref: innerRef,
    });

    // Assign multiple refs to a component by wrapping the ref assignments in a single useCallback
    const setRefs = useCallback(
      (node: HTMLElement) => {
        // Refs from useRef need to have the node assigned to `current`
        innerRef.current = node;
        // Callback refs, like the one from `useInView`, are functions that take the node as an arg
        inViewRef(node);
        animateInViewRef(node);
      },
      [innerRef, inViewRef, animateInViewRef],
    );

    useEffect(() => {
      if (innerRef.current) {
        updateSectionRefs(id, innerRef.current);
      }

      return () => {
        updateSectionRefs(id, null);
      };
    }, [id, innerRef, updateSectionRefs]);

    useEffect(() => {
      updateSectionThemes(id, sectionTheme);
    }, [id, sectionTheme, updateSectionThemes]);

    useEffect(() => {
      updateSectionHeights(id, height);
    }, [id, height, updateSectionHeights]);

    useLayoutEffect(() => {
      if (inView) {
        setCurrentSectionId(id);
        setCurrentTheme(sectionTheme);
      }
    }, [
      inView,
      id,
      sectionTheme,
      sectionThemesMap,
      setCurrentSectionId,
      setCurrentTheme,
    ]);

    const dividerVariants = {
      initial: { scaleX: 0, originX: 0 },
      animate: {
        scaleX: 1,
        originX: 0,
        transition: { ...lightSpring, delay: index / 10 },
      },
    };

    return (
      <section
        ref={setRefs}
        id={id}
        className={clsx(styles.section, sectionTheme, className)}
        {...props}
      >
        {variant === "full" ? (
          children
        ) : (
          <>
            <m.hr
              animate={animateInView ? "animate" : "initial"}
              variants={dividerVariants}
            />
            <m.div
              className={styles.inner}
              animate={animateInView ? "animate" : "initial"}
              transition={lightSpring}
              variants={fadeUpVariant}
            >
              {title && (
                <SplitText
                  as="h2"
                  className={styles.title}
                  splitBy="word"
                  variants={staggeredSlideUpVariant(0.01, index / 10)}
                  triggerOnce={triggerOnce}
                >
                  {title}
                </SplitText>
              )}
              <div className={styles.content}>
                {mdxSource && (
                  <div className={styles.mdxContent}>
                    <MDXContent mdxSource={mdxSource} />
                  </div>
                )}
                {children}
              </div>
            </m.div>
          </>
        )}
      </section>
    );
  },
);
Section.displayName = "Section";

export default Section;
