import React from 'react';
import { type LayoutChangeEvent, View, TextInput, type TextInputProps, TouchableNativeFeedback } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
	type SharedValue,
	useAnimatedProps,
	useAnimatedStyle,
	useDerivedValue,
	useSharedValue,
	withTiming
} from 'react-native-reanimated';
import { scheduleOnRN } from 'react-native-worklets';

import styles from './styles';
import { useTheme } from '../../theme';
import { SEEK_HIT_SLOP, THUMB_SEEK_SIZE, ACTIVE_OFFSET_X, DEFAULT_TIME_LABEL } from './constants';

const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

interface ISeek {
	duration: SharedValue<number>;
	currentTime: SharedValue<number>;
	loaded: boolean;
	onChangeTime: (time: number) => Promise<void>;
}

function clamp(value: number, min: number, max: number) {
	'worklet';

	return Math.min(Math.max(value, min), max);
}

// https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/
const formatTime = (ms: number) => {
	'worklet';

	const minutes = Math.floor(ms / 60);
	const remainingSeconds = Math.floor(ms % 60);
	const formattedMinutes = String(minutes).padStart(2, '0');
	const formattedSeconds = String(remainingSeconds).padStart(2, '0');
	return `${formattedMinutes}:${formattedSeconds}`;
};

const Seek = ({ currentTime, duration, loaded = false, onChangeTime }: ISeek) => {
	const { colors } = useTheme();

	const maxWidth = useSharedValue(1);
	const translateX = useSharedValue(0);
	const timeLabel = useSharedValue(DEFAULT_TIME_LABEL);
	const scale = useSharedValue(1);
	const isPanning = useSharedValue(false);
	const contextX = useSharedValue(0);

	const styleLine = useAnimatedStyle(() => ({
		width: translateX.value
	}));

	const styleThumb = useAnimatedStyle(() => ({
		transform: [{ translateX: translateX.value - THUMB_SEEK_SIZE / 2 }, { scale: scale.value }]
	}));

	const onLayout = (event: LayoutChangeEvent) => {
		const { width } = event.nativeEvent.layout;
		maxWidth.value = width;
	};

	const panGesture = Gesture.Pan()
		.enabled(loaded)
		.activeOffsetX([-ACTIVE_OFFSET_X, ACTIVE_OFFSET_X])
		.onStart(() => {
			isPanning.value = true;
			contextX.value = translateX.value;
			scale.value = withTiming(1.3, { duration: 150 });
		})
		.onUpdate(event => {
			const newX = contextX.value + event.translationX;
			translateX.value = clamp(newX, 0, maxWidth.value);
		})
		.onEnd(() => {
			scheduleOnRN(onChangeTime, Math.round(currentTime.value * 1000));
		})
		.onFinalize((_, didSucceed) => {
			if (isPanning.value && !didSucceed) {
				translateX.value = contextX.value;
				currentTime.value = (contextX.value * duration.value) / maxWidth.value || 0;
			}

			isPanning.value = false;
			scale.value = withTiming(1, { duration: 150 });
		});

	useDerivedValue(() => {
		if (isPanning.value) {
			// When the user is panning, always the currentTime.value is been set different from the currentTime provided by
			// the audio in progress
			currentTime.value = (translateX.value * duration.value) / maxWidth.value || 0;
		} else {
			translateX.value = (currentTime.value * maxWidth.value) / duration.value || 0;
		}
		timeLabel.value = formatTime(currentTime.value);
	}, [translateX, maxWidth, duration, isPanning, currentTime]);

	const timeLabelAnimatedProps = useAnimatedProps(() => {
		if (currentTime.value !== 0) {
			return {
				text: timeLabel.value
			} as TextInputProps;
		}
		return {
			text: formatTime(duration.value)
		} as TextInputProps;
	}, [timeLabel, duration, currentTime]);

	const thumbColor = loaded ? colors.buttonBackgroundPrimaryDefault : colors.buttonBackgroundPrimaryDisabled;

	// TouchableNativeFeedback is avoiding do a long press message when seeking the audio
	return (
		<TouchableNativeFeedback>
			<View style={styles.seekContainer}>
				<AnimatedTextInput
					defaultValue={DEFAULT_TIME_LABEL}
					editable={false}
					style={[styles.duration, { color: colors.fontDefault }]}
					animatedProps={timeLabelAnimatedProps}
				/>
				<View style={styles.seek} onLayout={onLayout}>
					<View style={[styles.line, { backgroundColor: colors.strokeLight }]}>
						<Animated.View style={[styles.line, styleLine, { backgroundColor: colors.buttonBackgroundPrimaryDefault }]} />
					</View>
					<GestureDetector gesture={panGesture}>
						<Animated.View hitSlop={SEEK_HIT_SLOP} style={[styles.thumbSeek, { backgroundColor: thumbColor }, styleThumb]} />
					</GestureDetector>
				</View>
			</View>
		</TouchableNativeFeedback>
	);
};

export default Seek;
