import { useSharedValue } from "react-native-reanimated";

import { useKeyboardHandler } from "../../../hooks";
import useScrollState from "../../hooks/useScrollState";

import {
  computeIOSContentOffset,
  getEffectiveHeight,
  getScrollEffective,
  getVisibleMinimumPaddingFraction,
  isScrollAtEnd,
  shouldShiftContent,
} from "./helpers";

import type { UseChatKeyboardOptions, UseChatKeyboardReturn } from "./types";
import type { AnimatedRef } from "react-native-reanimated";
import type Reanimated from "react-native-reanimated";

/**
 * Hook that manages keyboard-driven scrolling for chat-style scroll views.
 * Calculates padding (extra scrollable space) and content shift values,
 * using iOS-specific strategy (contentOffset set once in onStart).
 *
 * @param scrollViewRef - Animated ref to the scroll view.
 * @param options - Configuration for inverted and keyboardLiftBehavior.
 * @returns Shared values for padding and contentOffsetY.
 * @example
 * ```tsx
 * const { padding, contentOffsetY } = useChatKeyboard(ref, {
 *   inverted: false,
 *   keyboardLiftBehavior: "always",
 * });
 * ```
 */
function useChatKeyboard(
  scrollViewRef: AnimatedRef<Reanimated.ScrollView>,
  options: UseChatKeyboardOptions,
): UseChatKeyboardReturn {
  const {
    inverted,
    keyboardLiftBehavior,
    freeze,
    offset,
    blankSpace,
    extraContentPadding,
  } = options;

  const padding = useSharedValue(0);
  const currentHeight = useSharedValue(0);
  const contentOffsetY = useSharedValue(0);
  const targetKeyboardHeight = useSharedValue(0);
  const prevAbsorption = useSharedValue(0);

  const {
    layout,
    size,
    offset: scroll,
    onLayout,
    onContentSizeChange,
  } = useScrollState(scrollViewRef);

  useKeyboardHandler(
    {
      onStart: (e) => {
        "worklet";

        if (freeze.value) {
          return;
        }

        if (e.height > 0) {
          // eslint-disable-next-line react-compiler/react-compiler
          targetKeyboardHeight.value = e.height;
        }

        const effective = getEffectiveHeight(
          e.height,
          targetKeyboardHeight.value,
          offset,
        );

        const atEnd = isScrollAtEnd(
          scroll.value,
          layout.value.height,
          size.value.height,
          inverted,
        );

        // Scale minimum padding absorption by how much of it is visible.
        // Fully visible → full absorption; fully off-screen → no absorption.
        const visibleFraction = getVisibleMinimumPaddingFraction(
          scroll.value,
          layout.value.height,
          size.value.height,
          blankSpace.value,
          inverted,
        );
        const visiblePadding = visibleFraction * blankSpace.value;
        const minimumPaddingAbsorbed = Math.max(
          0,
          visiblePadding - extraContentPadding.value,
        );
        const scrollEffective = getScrollEffective(
          effective,
          minimumPaddingAbsorbed,
        );
        const actualTotalPadding = Math.max(
          blankSpace.value,
          effective + extraContentPadding.value,
        );

        // persistent mode: when keyboard shrinks, clamp to valid range
        if (
          keyboardLiftBehavior === "persistent" &&
          effective < padding.value
        ) {
          padding.value = effective;
          prevAbsorption.value = minimumPaddingAbsorbed;

          if (inverted) {
            const maxScroll = Math.max(
              size.value.height - layout.value.height,
              0,
            );

            contentOffsetY.value = Math.max(
              -actualTotalPadding,
              Math.min(scroll.value, maxScroll),
            );
          } else {
            const maxScroll = Math.max(
              size.value.height - layout.value.height + actualTotalPadding,
              0,
            );

            contentOffsetY.value = Math.max(
              0,
              Math.min(scroll.value, maxScroll),
            );
          }

          return;
        }

        // never mode: when keyboard shrinks, clamp to valid range
        // to avoid ghost padding
        if (
          keyboardLiftBehavior === "never" &&
          effective < padding.value &&
          atEnd
        ) {
          padding.value = effective;
          prevAbsorption.value = minimumPaddingAbsorbed;

          if (inverted) {
            const maxScroll = Math.max(
              size.value.height - layout.value.height,
              0,
            );

            contentOffsetY.value = Math.max(
              -actualTotalPadding,
              Math.min(scroll.value, maxScroll),
            );
          } else {
            const maxScroll = Math.max(
              size.value.height - layout.value.height + actualTotalPadding,
              0,
            );

            contentOffsetY.value = Math.max(
              0,
              Math.min(scroll.value, maxScroll),
            );
          }

          return;
        }

        // Undo only the scroll displacement that was actually applied
        // (not the full padding, which includes the absorbed portion).
        // Use the stored absorption from the previous event so that
        // the unwind matches the shift that was originally applied.
        const prevScrollEffective = getScrollEffective(
          padding.value,
          prevAbsorption.value,
        );
        const relativeScroll = inverted
          ? scroll.value + prevScrollEffective
          : scroll.value - prevScrollEffective;

        padding.value = effective;
        prevAbsorption.value = minimumPaddingAbsorbed;

        if (!shouldShiftContent(keyboardLiftBehavior, atEnd)) {
          // Preserve current scroll position so animated props
          // don't re-apply a stale contentOffset when padding changes
          contentOffsetY.value = scroll.value;

          return;
        }

        // When blankSpace fully absorbs the keyboard opening, preserve current scroll position
        // (only when keyboard is open — effective > 0 — not when closing)
        if (
          scrollEffective === 0 &&
          minimumPaddingAbsorbed > 0 &&
          effective > 0
        ) {
          contentOffsetY.value = scroll.value;

          return;
        }

        contentOffsetY.value = computeIOSContentOffset(
          relativeScroll,
          scrollEffective,
          size.value.height,
          layout.value.height,
          inverted,
          actualTotalPadding,
        );
      },
      onMove: () => {
        "worklet";

        // iOS doesn't need per-frame updates (contentOffset handles it)
      },
      onEnd: (e) => {
        "worklet";

        if (freeze.value) {
          return;
        }

        const effective = getEffectiveHeight(
          e.height,
          targetKeyboardHeight.value,
          offset,
        );

        padding.value = effective;
      },
    },
    [inverted, keyboardLiftBehavior, offset, extraContentPadding],
  );

  return {
    padding,
    currentHeight,
    contentOffsetY,
    scroll,
    layout,
    size,
    onLayout,
    onContentSizeChange,
  };
}

export { useChatKeyboard };
