import React, { Component } from "react";
import { Text } from "@plusscommunities/pluss-core-app/components";
import {
	Dimensions,
	Image,
	ScrollView,
	Modal,
	View,
	TouchableOpacity,
	StyleSheet,
	LogBox,
	Alert,
} from "react-native";
import * as ImageManipulator from "expo-image-manipulator";
import * as MediaLibrary from "expo-media-library";
import { File, Paths } from "expo-file-system";
import PropTypes from "prop-types";
import AutoHeightImage from "react-native-auto-height-image";
import {
	withSafeAreaInsets,
	SafeAreaView,
	SafeAreaProvider,
} from "react-native-safe-area-context";
import ImageCropOverlay from "./ImageCropOverlay";

const { width } = Dimensions.get("window");

LogBox.ignoreLogs([
	"componentWillReceiveProps",
	"componentWillUpdate",
	"componentWillMount",
]);
LogBox.ignoreLogs([
	"Warning: componentWillMount is deprecated",
	"Warning: componentWillReceiveProps is deprecated",
	"Module RCTImageLoader requires",
]);

class ExpoImageManipulator extends Component {
	constructor(props) {
		super(props);
		const { squareAspect } = this.props;
		this.state = {
			cropMode: false,
			processing: false,
			squareAspect,
			contentHeight: Dimensions.get("window").height - 100, // fallback until onLayout fires
		};

		this.scrollOffset = 0;
		this.currentPos = { left: 0, top: 0 };
		this.currentSize = { width: 0, height: 0 };
		this.maxSizes = { width: 0, height: 0 };
		this.actualSize = { width: 0, height: 0 };
	}

	async componentDidMount() {
		await this.onConvertImageToEditableSize();

		// Set cropping by default
		this.setState({ cropMode: true });
	}

	async onConvertImageToEditableSize() {
		try {
			const {
				photo: { uri: rawUri },
			} = this.props;

			// expo-image-manipulator only works with file:// URIs.
			// Resolve ph:// (iOS Photo Library) and https:// (remote/stock images) first.
			let resolvedUri = rawUri;
			if (rawUri?.startsWith("ph://")) {
				const assetId = rawUri.replace("ph://", "");
				const assetInfo = await MediaLibrary.getAssetInfoAsync(assetId, {
					shouldDownloadFromNetwork: true,
				});
				resolvedUri = assetInfo.localUri || rawUri;
			} else if (rawUri?.startsWith("http")) {
				const ext = rawUri.split(".").pop()?.split("?")[0] || "jpg";
				const file = await File.downloadFileAsync(
					rawUri,
					new File(Paths.cache, `crop_${Date.now()}.${ext}`),
					{ idempotent: true },
				);
				resolvedUri = file.uri;
			}

			const manipulatePromise = ImageManipulator.manipulateAsync(resolvedUri, [
				{ resize: { width: 1080 } },
			]);
			// manipulateAsync can hang forever on non-file:// URIs (promise never settles).
			// Promise.race with a timeout ensures the catch block fires.
			const timeoutPromise = new Promise((_, reject) =>
				setTimeout(
					() => reject(new Error("Image processing timed out")),
					15000,
				),
			);
			const { uri, width, height } = await Promise.race([
				manipulatePromise,
				timeoutPromise,
			]);
			this.setState({ uri });
			this.actualSize.width = width;
			this.actualSize.height = height;
		} catch (e) {
			console.log("onConvertImageToEditableSize error", e);
			this.onToggleModal();
			Alert.alert(
				"Error",
				"Unable to prepare this image for editing. Please try a different image.",
			);
		}
	}

	onToggleModal = () => {
		this.props.onToggleModal();
		this.setState({ cropMode: false });
	};

	onCropImage = (callback = null) => {
		this.setState({ processing: true });
		const { uri } = this.state;
		Image.getSize(uri, async (actualWidth, actualHeight) => {
			let cropObj = this.getCropBounds(actualWidth, actualHeight);
			if (cropObj.height > 0 && cropObj.width > 0) {
				let uriToCrop = uri;
				const {
					uri: uriCroped,
					base64,
					width: croppedWidth,
					height: croppedHeight,
				} = await this.crop(cropObj, uriToCrop);

				this.actualSize.width = croppedWidth;
				this.actualSize.height = croppedHeight;

				this.setState(
					{
						uri: uriCroped,
						base64,
						cropMode: false,
						processing: false,
					},
					() => {
						if (callback) callback();
					},
				);
			} else {
				this.setState({ cropMode: false, processing: false }, () => {
					if (callback) callback();
				});
			}
		});
	};

	onHandleScroll = (event) => {
		this.scrollOffset = event.nativeEvent.contentOffset.y;
	};

	getCropBounds = (actualWidth, actualHeight) => {
		const imageRatio = actualHeight / actualWidth;
		const { contentHeight } = this.state;
		const originalHeight = contentHeight;
		let renderedImageWidth =
			imageRatio < originalHeight / width ? width : originalHeight / imageRatio;
		let renderedImageHeight =
			imageRatio < originalHeight / width ? width * imageRatio : originalHeight;

		let renderedImageY = (originalHeight - renderedImageHeight) / 2.0;
		let renderedImageX = (width - renderedImageWidth) / 2.0;

		const renderImageObj = {
			left: renderedImageX,
			top: renderedImageY,
			width: renderedImageWidth,
			height: renderedImageHeight,
		};
		const cropOverlayObj = {
			left: this.currentPos.left,
			top: this.currentPos.top,
			width: this.currentSize.width,
			height: this.currentSize.height,
		};

		var intersectAreaObj = {};

		let x = Math.max(renderImageObj.left, cropOverlayObj.left);
		let num1 = Math.min(
			renderImageObj.left + renderImageObj.width,
			cropOverlayObj.left + cropOverlayObj.width,
		);
		let y = Math.max(renderImageObj.top, cropOverlayObj.top);
		let num2 = Math.min(
			renderImageObj.top + renderImageObj.height,
			cropOverlayObj.top + cropOverlayObj.height,
		);
		if (num1 >= x && num2 >= y)
			intersectAreaObj = {
				originX: (x - renderedImageX) * (actualWidth / renderedImageWidth),
				originY: (y - renderedImageY) * (actualWidth / renderedImageWidth),
				width: (num1 - x) * (actualWidth / renderedImageWidth),
				height: (num2 - y) * (actualWidth / renderedImageWidth),
			};
		else {
			intersectAreaObj = {
				originX: x - renderedImageX,
				originY: y - renderedImageY,
				width: 0,
				height: 0,
			};
		}
		return intersectAreaObj;
	};

	crop = async (cropObj, uri) => {
		const { saveOptions } = this.props;
		if (cropObj.height > 0 && cropObj.width > 0) {
			const manipResult = await ImageManipulator.manipulateAsync(
				uri,
				[
					{
						crop: cropObj,
					},
				],
				saveOptions,
			);
			return manipResult;
		}
		return {
			uri: null,
			base64: null,
		};
	};

	calculateMaxSizes = (event) => {
		let w1 = event.nativeEvent.layout.width || 100;
		let h1 = event.nativeEvent.layout.height || 100;
		if (this.state.squareAspect) {
			if (w1 < h1) h1 = w1;
			else w1 = h1;
		}
		this.maxSizes.width = w1;
		this.maxSizes.height = h1;
	};

	// eslint-disable-next-line camelcase
	async UNSAFE_componentWillReceiveProps() {
		await this.onConvertImageToEditableSize();
	}

	onContentLayout = (event) => {
		this.setState({
			contentHeight: event.nativeEvent.layout.height,
		});
	};

	render() {
		const { isVisible, onPictureChoosed } = this.props;
		const { uri, base64, cropMode, processing, contentHeight } = this.state;

		const imageRatio = this.actualSize.height / this.actualSize.width;
		const originalHeight = contentHeight;
		const topMargin = (this.props.insets?.top ?? 0) + 50;

		const cropRatio = originalHeight / width;

		const cropWidth =
			imageRatio < cropRatio ? width : originalHeight / imageRatio;
		const cropHeight =
			imageRatio < cropRatio ? width * imageRatio : originalHeight;

		const cropInitialTop = (originalHeight - cropHeight) / 2.0;
		const cropInitialLeft = (width - cropWidth) / 2.0;

		if (this.currentSize.width == 0 && cropMode) {
			this.currentSize.width = cropWidth;
			this.currentSize.height = cropHeight;

			this.currentPos.top = cropInitialTop;
			this.currentPos.left = cropInitialLeft;
		}

		return (
			<Modal
				animationType="slide"
				transparent={true}
				visible={isVisible}
				hardwareAccelerated
				onRequestClose={() => {
					this.onToggleModal();
				}}
			>
				{uri !== undefined ? (
					<SafeAreaProvider>
						<View style={styles.container}>
							<SafeAreaView style={[{ width }, styles.headerContainer]}>
								{!cropMode ? (
									<View
										style={[
											styles.headerButtonContainer,
											{ justifyContent: "flex-end" },
										]}
									>
										<TouchableOpacity
											onPress={() => {
												onPictureChoosed({ uri, base64 });
												this.onToggleModal();
											}}
											style={styles.headerButton}
										>
											<Text style={styles.headerButtonText}>{"Done"}</Text>
										</TouchableOpacity>
									</View>
								) : (
									<View style={styles.headerButtonContainer}>
										<TouchableOpacity
											onPress={this.onToggleModal}
											style={styles.headerButton}
										>
											<Text style={styles.headerButtonText}>{"Cancel"}</Text>
										</TouchableOpacity>
										<TouchableOpacity
											onPress={() => {
												this.onCropImage(() => {
													onPictureChoosed({
														uri: this.state.uri,
														base64: this.state.base64,
													});
													this.onToggleModal();
												});
											}}
											style={styles.headerButton}
										>
											<Text style={styles.headerButtonText}>
												{processing ? "Processing" : "Done"}
											</Text>
										</TouchableOpacity>
									</View>
								)}
							</SafeAreaView>
							<SafeAreaView
								onLayout={this.onContentLayout}
								style={[styles.contentContainer, { width }]}
								edges={["bottom"]}
							>
								<ScrollView
									style={{ position: "relative", flex: 1 }}
									contentContainerStyle={{ backgroundColor: "black" }}
									maximumZoomScale={5}
									minimumZoomScale={0.5}
									onScroll={this.onHandleScroll}
									bounces={false}
									showsHorizontalScrollIndicator={false}
									showsVerticalScrollIndicator={false}
									ref={(c) => (this.scrollView = c)}
									scrollEventThrottle={16}
									scrollEnabled={false}
									pinchGestureEnabled={false}
								>
									<AutoHeightImage
										style={{ backgroundColor: "black" }}
										source={{ uri }}
										resizeMode={imageRatio >= 1 ? "contain" : "contain"}
										width={width}
										height={originalHeight}
										onLayout={this.calculateMaxSizes}
									/>
									{!!cropMode && (
										<ImageCropOverlay
											onLayoutChanged={(top, left, width, height) => {
												this.currentSize.width = width;
												this.currentSize.height = height;
												this.currentPos.top = top;
												this.currentPos.left = left;
											}}
											initialWidth={cropWidth}
											initialHeight={cropHeight}
											initialTop={cropInitialTop}
											initialLeft={cropInitialLeft}
											minHeight={100}
											minWidth={100}
											topMargin={topMargin}
										/>
									)}
								</ScrollView>
							</SafeAreaView>
						</View>
					</SafeAreaProvider>
				) : (
					<View style={styles.container}>
						<Text style={styles.prepareMessageText}>Preparing...</Text>
					</View>
				)}
			</Modal>
		);
		// }
	}
}

const styles = StyleSheet.create({
	headerContainer: {
		flexDirection: "row",
		backgroundColor: "black",
		justifyContent: "space-between",
	},
	headerButtonContainer: {
		flex: 1,
		flexDirection: "row",
		justifyContent: "space-between",
	},
	headerButton: {
		marginHorizontal: 10,
		width: 60,
		height: 40,
		alignItems: "center",
		justifyContent: "center",
	},
	headerButtonText: {
		fontWeight: "500",
		color: "white",
		fontSize: 18,
	},
	contentContainer: {
		flex: 1,
		backgroundColor: "black",
	},
	container: {
		flex: 1,
		backgroundColor: "#000d",
		alignItems: "center",
		justifyContent: "center",
	},
	prepareMessageText: {
		fontFamily: "sf-bold",
		fontSize: 20,
		color: "#fff",
	},
});

export default withSafeAreaInsets(ExpoImageManipulator);

ExpoImageManipulator.defaultProps = {
	onPictureChoosed: ({ uri, base64 }) => console.log("URI:", uri, base64),
	dragVelocity: 100,
	resizeVelocity: 50,
	saveOptions: {
		compress: 1,
		format: ImageManipulator.SaveFormat.PNG,
		base64: false,
	},
};

ExpoImageManipulator.propTypes = {
	isVisible: PropTypes.bool.isRequired,
	onPictureChoosed: PropTypes.func,
	saveOptions: PropTypes.object,
	photo: PropTypes.object.isRequired,
	onToggleModal: PropTypes.func.isRequired,
	dragVelocity: PropTypes.number,
	resizeVelocity: PropTypes.number,
};
