import React, {
  ForwardedRef,
  forwardRef,
  memo,
  useCallback,
  useEffect,
  useId,
  useImperativeHandle,
  useMemo,
  useRef,
} from 'react';
import {LayoutChangeEvent, View} from 'react-native';
import Animated, {
  clamp,
  SharedValue,
  useAnimatedProps,
  useAnimatedStyle,
  useDerivedValue,
  useSharedValue,
} from 'react-native-reanimated';
import Svg, {Rect} from 'react-native-svg';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
import {scheduleOnRN} from 'react-native-worklets';
import {SPACING, useTheme} from '@shared/theme';

import {PLAYER_THUMB_SIZE, styles} from './styles';
import {WaveForm} from '../WaveForms/WaveForm';

const AnimatedRect = Animated.createAnimatedComponent(Rect);

interface AudioTimelineRef {
  setProgress: (progress: number) => void;
  checkIsDragging: () => boolean;
  isDragging: SharedValue<boolean>;
}

interface Props {
  progress: number; // 0 to 1
  duration: number;
  thumbColor?: string;
  normalizedBarsHeights?: number[];
  waveformHeight?: number;
  onChangeProgress?: (value: number) => void;
  onDragging?: (isDragging: boolean) => void;
  onDraggingEnd?: () => void;
}

function AudioTimelineCmp(
  {
    onChangeProgress,
    onDragging,
    onDraggingEnd,
    waveformHeight = SPACING.x3,
    thumbColor: propThumbColor,
    normalizedBarsHeights,
  }: {
    onChangeProgress?: (value: number) => void;
    onDragging?: (isDragging: boolean) => void;
    onDraggingEnd?: () => void;
    thumbColor?: string;
    waveformHeight?: number;
    normalizedBarsHeights?: number[];
  },
  ref: ForwardedRef<AudioTimelineRef>,
) {
  const {theme} = useTheme();
  const patternInstanceId = useId().replace(/:/g, '');
  const activePatternId = `${patternInstanceId}-wave`;
  const inactivePatternId = `${patternInstanceId}-inactive`;
  const waveformWidth = useSharedValue(0);
  const prevThumbX = useSharedValue(0);
  const progress = useSharedValue(0);
  const isDragging = useSharedValue(false);
  const thumbColor = propThumbColor || theme.brand['500'];

  const thumbX = useDerivedValue(() => {
    const w = waveformWidth.value;
    if (w <= 0) {
      return 0;
    }
    return clamp(w * progress.value, 0, w);
  });

  const gesturePan = Gesture.Pan()
    .hitSlop({left: 50, right: 50})
    .onStart(() => {
      isDragging.value = true;
      if (onDragging) {
        scheduleOnRN(onDragging, true);
      }
      prevThumbX.value = thumbX.value;
    })
    .onUpdate(ev => {
      const w = waveformWidth.value;
      if (w <= 0) {
        return;
      }
      const val = clamp(ev.x / w, 0, 1);

      progress.value = val;
    })
    .onEnd(() => {
      if (onDragging) {
        scheduleOnRN(onDragging, false);
      }
      if (onDraggingEnd) {
        scheduleOnRN(onDraggingEnd);
      }

      if (onChangeProgress) {
        scheduleOnRN(onChangeProgress, progress.value);
      }

      isDragging.value = false;
    });

  const gestureTap = Gesture.Tap().onStart(e => {
    const w = waveformWidth.value;
    if (w <= 0) {
      return;
    }
    const val = clamp(e.x / w, 0, 1);
    progress.value = val;
    if (onChangeProgress) {
      scheduleOnRN(onChangeProgress, val);
    }
  });

  const gesture = Gesture.Simultaneous(gesturePan, gestureTap);

  const thumbAnimatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateX: clamp(
            thumbX.value - PLAYER_THUMB_SIZE / 2,
            0,
            waveformWidth.value - PLAYER_THUMB_SIZE,
          ),
        },
      ],
    };
  });

  const reactAnimatedProps = useAnimatedProps(() => {
    const w = waveformWidth.value;
    const x = thumbX.value;
    /** Numeric width — `width="100%"` + animated `x` often glitches blank frames on RN SVG. */
    const overlayWidth = Math.max(0, w - x);
    return {
      width: overlayWidth,
      x,
    };
  });

  useImperativeHandle(ref, () => ({
    setProgress: (val: number) => {
      progress.value = val;
    },
    checkIsDragging: () => isDragging.value,
    isDragging: isDragging,
  }));

  const onLayout = useCallback((e: LayoutChangeEvent) => {
    if (!waveformWidth.value) {
      waveformWidth.value = e.nativeEvent.layout.width;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const memoStyles = useMemo(() => {
    return {
      container: [styles.container, {minHeight: waveformHeight}],
      waveformWrapper: [styles.waveformWrapper, {height: waveformHeight}],
      thumb: [
        styles.thumb,
        {
          backgroundColor: thumbColor,
          bottom: waveformHeight / 2 - PLAYER_THUMB_SIZE / 2,
        },
        thumbAnimatedStyle,
      ],
    };
  }, [thumbColor, waveformHeight, thumbAnimatedStyle]);

  return (
    <Animated.View style={memoStyles.container}>
      <GestureDetector gesture={gesture}>
        <View style={memoStyles.waveformWrapper}>
          <Animated.View style={memoStyles.thumb} />
          <View style={styles.svgClip}>
            <Svg onLayout={onLayout} style={styles.svg} height={waveformHeight}>
              <WaveForm
                patternId={activePatternId}
                normalizedBarHeights={normalizedBarsHeights}
                maxHeight={waveformHeight}
              />
              <WaveForm
                patternId={inactivePatternId}
                color={theme.icon.neutral.lighter}
                normalizedBarHeights={normalizedBarsHeights}
                maxHeight={waveformHeight}
              />
              <Rect
                x={0}
                y={0}
                width={'100%'}
                height={waveformHeight}
                fill={`url(#${activePatternId})`}
              />
              <AnimatedRect
                y={0}
                opacity={1}
                height={waveformHeight}
                fill={`url(#${inactivePatternId})`}
                animatedProps={reactAnimatedProps}
              />
            </Svg>
          </View>
        </View>
      </GestureDetector>
    </Animated.View>
  );
}

const Timeline = memo(forwardRef(AudioTimelineCmp));

export function AudioTimeline({
  progress,
  duration,
  onChangeProgress,
  onDragging,
  onDraggingEnd,
  thumbColor,
  normalizedBarsHeights,
  waveformHeight,
}: Props) {
  const audioTimelineRef = useRef<Nullable<AudioTimelineRef>>(null);

  const updateProgress = useCallback(
    (value: number) => {
      onChangeProgress?.(value * duration);
    },
    [duration, onChangeProgress],
  );

  useEffect(() => {
    if (!audioTimelineRef.current?.isDragging.value) {
      audioTimelineRef.current?.setProgress(progress ?? 0);
    }
  }, [progress]);

  return (
    <Timeline
      ref={audioTimelineRef}
      waveformHeight={waveformHeight}
      normalizedBarsHeights={normalizedBarsHeights}
      thumbColor={thumbColor}
      onChangeProgress={updateProgress}
      onDraggingEnd={onDraggingEnd}
      onDragging={onDragging}
    />
  );
}
