import { useState, useEffect, useRef, useCallback, MutableRefObject } from 'react';
import { style } from 'typestyle';
import { useStore } from '../services/store';
import { IMedia } from '../types';
import { EventMapKeys } from '../types/events';

// Background-scroll lock classes applied to <body> and <html> while a player
// is open. The vertical offset is driven by a CSS variable so we can pin the
// page at its current scroll position.
const SCROLL_LOCK_TOP_VAR = '--adl-player-scroll-lock-top';

// Animation constants shared by all player components
export const PLAYER_ANIMATION_DURATION_MS = 300;
export const PLAYER_ANIMATION_DURATION_SEC = PLAYER_ANIMATION_DURATION_MS / 1000;

// Duration of the slide transition between posts
export const SLIDE_DURATION_MS = 260;

export interface UsePlayerNavigationProps {
  post: IMedia;
  onClose: () => void;
  onPostChange: (post: IMedia | null) => void;
  openedEventName?: EventMapKeys;
  closedEventName?: EventMapKeys;
  navigationEventName?: EventMapKeys;
  swipeDirection?: 'horizontal' | 'vertical';
}

export interface UsePlayerNavigationResult {
  currentDisplayPost: IMedia;
  nextDisplayPost: IMedia | null;
  prevDisplayPost: IMedia | null;
  isMuted: boolean;
  isPlaying: boolean;
  isVisible: boolean;
  isClosing: boolean;
  showNextIndicator: boolean;
  thumbnailImage: string | null | undefined;
  hasUserInteractedRef: MutableRefObject<boolean>;
  slideOffset: number;
  isSliding: boolean;
  /** CSS axis used for the slide transition — 'X' for horizontal, 'Y' for vertical. */
  slideAxis: 'X' | 'Y';
  handleClose: () => void;
  handleNextPost: () => void;
  handlePreviousPost: () => void;
  toggleMute: () => void;
  togglePlay: () => void;
}

// Current post with its immediate neighbours, always updated atomically.
interface NavState {
  current: IMedia;
  prev: IMedia | null;
  next: IMedia | null;
}

const getNeighbours = (
  medias: IMedia[],
  index: number,
): Pick<NavState, 'prev' | 'next'> => ({
  prev: index > 0 ? medias[index - 1] : null,
  next: index < medias.length - 1 ? medias[index + 1] : null,
});

/**
 * Shared hook for player navigation logic.
 * Used by both MobilePlayer and DesktopProductListPlayer.
 */
const usePlayerNavigation = ({
  post,
  onClose,
  onPostChange,
  openedEventName = 'mobilePlayerOpened',
  closedEventName = 'mobilePlayerClosed',
  navigationEventName = 'mobilePlayerNavigation',
  swipeDirection = 'vertical',
}: UsePlayerNavigationProps): UsePlayerNavigationResult => {
  const slideAxis: 'X' | 'Y' = swipeDirection === 'vertical' ? 'Y' : 'X';
  const store = useStore();
  const { medias } = store.data.content;

  // current/prev/next live in one object so they always update in the same render,
  // preventing any transient frame where two slides share the same post id.
  const [nav, setNav] = useState<NavState>(() => ({
    current: post,
    ...getNeighbours(medias, post.postIndex),
  }));

  const [userMutedPreference, setUserMutedPreference] = useState(true);
  const [isPlaying, setIsPlaying] = useState(true);
  const [isVisible, setIsVisible] = useState(false);
  const [isClosing, setIsClosing] = useState(false);
  const [slideOffset, setSlideOffset] = useState(0);
  const [isSliding, setIsSliding] = useState(false);

  const hasUserInteractedRef = useRef<boolean>(false);
  const isSlidingRef = useRef(false);
  const isForceMuted = nav.current.isMuted === true;
  const isMuted = isForceMuted || userMutedPreference;

  // Animate in on mount and fire opened event.
  useEffect(() => {
    store.triggerEvent(openedEventName, { post: nav.current }, store.data.id);
    const timer = setTimeout(() => setIsVisible(true), 50);
    return () => clearTimeout(timer);
  }, []);

  // Sync when the post prop is changed from outside (e.g. thumbnail click).
  useEffect(() => {
    if (post.id !== nav.current.id) {
      setNav({ current: post, ...getNeighbours(medias, post.postIndex) });
      setIsPlaying(true);
      hasUserInteractedRef.current = true;
    }
  }, [post]);

  // Prevent background scrolling while the player is open.

  // We pin <body> with `position: fixed`, lock <html> overflow,
  // then restore the scroll position on cleanup.
  useEffect(() => {
    const scrollY = window.scrollY || window.pageYOffset || 0;
    document.body.style.setProperty(SCROLL_LOCK_TOP_VAR, `-${scrollY}px`);
    document.body.classList.add(bodyScrollLockClass);
    document.documentElement.classList.add(htmlScrollLockClass);

    return () => {
      document.body.classList.remove(bodyScrollLockClass);
      document.documentElement.classList.remove(htmlScrollLockClass);
      document.body.style.removeProperty(SCROLL_LOCK_TOP_VAR);
      // "Instant" scroll avoids the "smooth" scroll back to position
      window.scrollTo({ top: scrollY, left: 0, behavior: 'instant' as ScrollBehavior });
    };
  }, []);

  // Shared slide logic — direction 'right' means going to next, 'left' to previous.
  const navigateTo = useCallback((
    target: IMedia,
    direction: 'left' | 'right',
  ) => {
    if (isSlidingRef.current) return;
    hasUserInteractedRef.current = true;
    isSlidingRef.current = true;
    setIsSliding(true);
    setSlideOffset(direction === 'right' ? -100 : 100);
    store.triggerEvent(navigationEventName, { direction, post: target }, store.data.id);
    setTimeout(() => {
      setNav({ current: target, ...getNeighbours(medias, target.postIndex) });
      setIsPlaying(true);
      onPostChange(target);
      setSlideOffset(0);
      setIsSliding(false);
      isSlidingRef.current = false;
    }, SLIDE_DURATION_MS);
  }, [medias, onPostChange, store, navigationEventName]);

  const handleClose = useCallback(() => {
    store.triggerEvent(closedEventName, { post: nav.current }, store.data.id);
    setIsClosing(true);
    setTimeout(onClose, PLAYER_ANIMATION_DURATION_MS);
  }, [onClose, nav.current, store, closedEventName]);

  const handleNextPost = useCallback(() => {
    if (nav.next) navigateTo(nav.next, 'right');
    else handleClose();
  }, [nav.next, navigateTo, handleClose]);
  const handlePreviousPost = useCallback(() => {
    if (nav.prev) navigateTo(nav.prev, 'left');
  }, [nav.prev, navigateTo]);

  const toggleMute = useCallback(() => {
    if (isForceMuted) return;
    setUserMutedPreference((prev) => !prev);
  }, [isForceMuted]);
  const togglePlay = useCallback(() => setIsPlaying((prev) => !prev), []);

  const showNextIndicator = !hasUserInteractedRef.current && nav.next !== null;

  const thumbnailImage =
    nav.current.type === 'video' && nav.current.thumbnail
      ? nav.current.thumbnail
      : nav.current.image;

  return {
    currentDisplayPost: nav.current,
    nextDisplayPost: nav.next,
    prevDisplayPost: nav.prev,
    isMuted,
    isPlaying,
    isVisible,
    isClosing,
    showNextIndicator,
    thumbnailImage,
    hasUserInteractedRef,
    slideOffset,
    isSliding,
    slideAxis,
    handleClose,
    handleNextPost,
    handlePreviousPost,
    toggleMute,
    togglePlay,
  };
};

const bodyScrollLockClass = style({
  position: 'fixed',
  top: `var(${SCROLL_LOCK_TOP_VAR}, 0)`,
  left: 0,
  right: 0,
  width: '100%',
  overflow: 'hidden',
});
const htmlScrollLockClass = style({ overflow: 'hidden' });

export default usePlayerNavigation;
