import { useCallback, useEffect, useState, useMemo } from '@wordpress/element';
import { dispatch, select as selectData, useSelect } from '@wordpress/data';
import { invert, isEqual } from 'lodash';
import {
	getCurrentOrder,
	getCurrentOrderItems,
	getCurrentOrderShipments,
	getFirstSelectableOriginAddress,
	getDefaultOutboundAddress,
	getDefaultReturnAddress,
	getSubItems,
	getShipmentsAutogeneratedFromLabels,
	getStoreOrigin,
} from 'utils';
import {
	LabelShipmentIdMap,
	OriginAddress,
	ReturnShipmentInfo,
	ShipmentItem,
	ShipmentDate,
} from 'types';
import { addressStore } from 'data/address';
import { labelPurchaseStore } from 'data/label-purchase';
import { LABEL_PURCHASE_STATUS } from 'data/constants';
import { getShipmentDefaultDates, SHIPMENT_TYPE } from 'utils';
import { ShipmentType } from 'types';

export function useShipmentState() {
	const [ currentShipmentId, setCurrentShipmentId ] = useState( '0' );
	const [ shipments, updateShipments ] = useState<
		Record< string, ShipmentItem[] >
	>( getCurrentOrderShipments() );
	const [ returnShipments, setReturnShipments ] = useState<
		Record< string, ReturnShipmentInfo >
	>( {} );
	const [ selections, setSelection ] = useState<
		Record< string, ShipmentItem[] >
	>( {
		0: [],
	} );

	const [ shipmentOrigins, setShipmentOrigins ] = useState<
		Record< string, OriginAddress | undefined >
	>( {
		0: getFirstSelectableOriginAddress(),
	} );

	// The most recently purchased label, that has not been refunded.
	const activePurchasedLabel =
		selectData( labelPurchaseStore ).getPurchasedLabel( currentShipmentId );

	const [ shipmentDates, setShipmentDates ] = useState<
		Record< string, ShipmentDate< Date > >
	>( {
		0: getShipmentDefaultDates( '0', activePurchasedLabel ),
	} );

	const findOriginAddressById = ( originId: string ) => {
		try {
			const store = selectData( addressStore );
			if ( store && typeof store.getOriginAddresses === 'function' ) {
				const origins = store.getOriginAddresses();
				return origins?.find( ( a ) => a.id === originId );
			}
		} catch {
			// Handle cases where store or method doesn't exist
		}
		return undefined;
	};

	const isReturnShipment = useCallback(
		( shipmentKey: string ): boolean => {
			return !! returnShipments[ shipmentKey ]?.isReturn;
		},
		[ returnShipments ]
	);

	const getDefaultAddressForShipmentType = useCallback(
		( shipmentKey: string ): OriginAddress | undefined => {
			if ( isReturnShipment( shipmentKey ) ) {
				return getDefaultReturnAddress();
			}
			return getDefaultOutboundAddress();
		},
		[ isReturnShipment ]
	);

	const setShipmentOrigin = useCallback(
		( originId: string ) => {
			const origin = findOriginAddressById( originId );

			if (
				! origin ||
				( origin &&
					isEqual( shipmentOrigins[ currentShipmentId ], origin ) )
			) {
				return;
			}

			setShipmentOrigins( ( prevState ) => ( {
				...prevState,
				[ currentShipmentId ]: origin,
			} ) );
		},
		[ currentShipmentId, shipmentOrigins ]
	);

	const purchasedLabelOrigin = useSelect(
		( select ) =>
			select( labelPurchaseStore ).getLabelOrigins( currentShipmentId ),
		[ currentShipmentId ]
	);

	const purchasedLabelDestination = useSelect(
		( select ) =>
			select( labelPurchaseStore ).getLabelDestinations(
				currentShipmentId
			),
		// eslint-disable-next-line react-hooks/exhaustive-deps -- we want this to update when the shipmentId or activePurchasedLabel changes
		[ currentShipmentId, activePurchasedLabel ]
	);

	const orderDestination = useSelect(
		( select ) => select( addressStore ).getOrderDestination(),
		// order destination is not dependent on the shipmentId or activePurchasedLabel changes
		[]
	);

	useEffect( () => {
		// Fetching the origin and destination addresses for the most recently purchased label doesn't check
		// if it has been refunded or not, so we check for "activePurchasedLabel" as well.
		// older implementations of purchasedLabelOrigin don't have the id property, so we check for it.
		if (
			activePurchasedLabel &&
			[
				LABEL_PURCHASE_STATUS.PURCHASED,
				LABEL_PURCHASE_STATUS.PURCHASE_IN_PROGRESS,
			].includes( activePurchasedLabel.status ) &&
			purchasedLabelOrigin?.id
		) {
			setShipmentOrigin( purchasedLabelOrigin.id );
		} else if ( ! shipmentOrigins[ currentShipmentId ] ) {
			const defaultAddress =
				getDefaultAddressForShipmentType( currentShipmentId );
			if ( defaultAddress ) {
				setShipmentOrigin( defaultAddress.id );
			}
		}
	}, [
		currentShipmentId,
		activePurchasedLabel,
		purchasedLabelOrigin,
		purchasedLabelDestination,
		shipments,
		setShipmentOrigin,
		orderDestination,
		shipmentOrigins,
		getDefaultAddressForShipmentType,
	] );

	const [ labelShipmentIdsToUpdate, setLabelShipmentIdsToUpdate ] =
		useState< LabelShipmentIdMap >( {} );

	const getShipmentWeight = useCallback(
		() =>
			shipments[ currentShipmentId ]?.reduce(
				( acc, { weight, quantity } ) =>
					acc + Number( weight || 0 ) * Number( quantity ),
				0
			),
		[ shipments, currentShipmentId ]
	);

	const resetSelections = ( shipmentIds: string[] ) => {
		setSelection(
			shipmentIds.reduce(
				( acc, key ) => ( { ...acc, [ key ]: [] } ),
				{}
			)
		);
	};

	const getShipmentItems = useCallback(
		( shipmentId = currentShipmentId ) => shipments[ shipmentId ],
		[ shipments, currentShipmentId ]
	);

	const getSelectionItems = useCallback(
		( shipmentId = currentShipmentId ) => selections[ shipmentId ],
		[ selections, currentShipmentId ]
	);

	const getShipmentOrigin = useCallback( (): OriginAddress => {
		if (
			activePurchasedLabel &&
			purchasedLabelOrigin &&
			[
				LABEL_PURCHASE_STATUS.PURCHASED,
				LABEL_PURCHASE_STATUS.PURCHASE_IN_PROGRESS,
			].includes( activePurchasedLabel.status )
		) {
			const foundOrigin = findOriginAddressById(
				purchasedLabelOrigin.id
			);
			if ( foundOrigin ) {
				return foundOrigin;
			}
		}

		const selectedOrigin =
			shipmentOrigins[ currentShipmentId ] ??
			getDefaultAddressForShipmentType( currentShipmentId );
		// If no origin address is available, return a default empty address
		if ( ! selectedOrigin ) {
			const storeOrigin = getStoreOrigin();
			return {
				id: '',
				name: '',
				address: '',
				address1: '',
				address2: '',
				city: '',
				state: storeOrigin.state,
				postcode: '',
				country: storeOrigin.country,
				phone: '',
				email: '',
				firstName: '',
				lastName: '',
				company: '',
				isVerified: false,
				defaultAddress: false,
			};
		}

		return selectedOrigin;
	}, [
		activePurchasedLabel,
		currentShipmentId,
		purchasedLabelOrigin,
		shipmentOrigins,
		getDefaultAddressForShipmentType,
	] );

	/**
	 * Returns the ship from address recorded at the time of purchase.
	 * This can differ from the current ship from address if the user has changed it.
	 * If the user has changed the address, the id will remain the same, but the address will be different.
	 */
	const getShipmentPurchaseOrigin = () =>
		activePurchasedLabel &&
		purchasedLabelOrigin &&
		activePurchasedLabel.status === LABEL_PURCHASE_STATUS.PURCHASED
			? purchasedLabelOrigin
			: null;

	const getShipmentDestination = useCallback( () => {
		if (
			activePurchasedLabel &&
			activePurchasedLabel.status === LABEL_PURCHASE_STATUS.PURCHASED &&
			purchasedLabelDestination
		) {
			return purchasedLabelDestination;
		}

		return orderDestination;
	}, [ activePurchasedLabel, orderDestination, purchasedLabelDestination ] );

	const setShipments = (
		newShipments: Record< string, ShipmentItem[] >,
		updatedShipmentIds?: LabelShipmentIdMap
	) => {
		if ( updatedShipmentIds ) {
			setLabelShipmentIdsToUpdate( updatedShipmentIds );
			dispatch( labelPurchaseStore ).stageLabelsNewShipmentIds(
				updatedShipmentIds
			);
		}

		updateShipments( newShipments );
	};

	const revertLabelShipmentIdsToUpdate = () => {
		dispatch( labelPurchaseStore ).stageLabelsNewShipmentIds(
			invert( labelShipmentIdsToUpdate )
		);
		setLabelShipmentIdsToUpdate( {} );
	};

	const hasVariations = useMemo(
		() =>
			Object.values( shipments )
				.flat()
				.some( ( item ) => {
					return !! item.variation?.length;
				} ),
		[ shipments ]
	);

	const hasMultipleShipments = useMemo(
		() => Object.values( shipments ).length > 1,
		[ shipments ]
	);

	const isExtraLabelPurchaseValid = () => {
		return selections[ currentShipmentId ]?.length > 0;
	};

	const resetShipmentAndSelection = useCallback( () => {
		const updatedShipments = {
			...shipments,
			[ currentShipmentId ]: undefined,
		};
		delete updatedShipments[ currentShipmentId ];

		const order = getCurrentOrder();

		dispatch( labelPurchaseStore ).updateShipments( {
			shipments: updatedShipments,
			orderId: `${ order?.id }`,
			shipmentIdsToUpdate: {},
		} );
		const orderItems = getCurrentOrderItems();
		const shipmentItems = orderItems.map( ( orderItem ) => ( {
			...orderItem,
			subItems: getSubItems( orderItem as ShipmentItem ),
		} ) );
		updateShipments( ( currentShipments ) => ( {
			...currentShipments,
			[ currentShipmentId ]: shipmentItems,
		} ) );
		setSelection( {
			...selections,
			[ currentShipmentId ]: shipmentItems,
		} );
	}, [ currentShipmentId, selections, shipments ] );

	const isShipmentAutogeneratedFromLabel = useCallback(
		() =>
			getShipmentsAutogeneratedFromLabels().includes(
				Number( currentShipmentId )
			),
		[ currentShipmentId ]
	);

	const setCurrentShipmentDate = useCallback(
		( shippingDate: Date, estimatedDeliveryDate?: Date ) => {
			setShipmentDates( ( prevState ) => ( {
				...prevState,
				[ currentShipmentId ]: { shippingDate, estimatedDeliveryDate },
			} ) );
		},
		[ currentShipmentId ]
	);

	const getCurrentShipmentDate = useCallback( () => {
		return shipmentDates[ currentShipmentId ];
	}, [ currentShipmentId, shipmentDates ] );

	/**
	 * Load return shipment data from purchased labels on initialization.
	 * This ensures parentShipmentId persists across page reloads.
	 */
	useEffect( () => {
		try {
			const store = selectData( labelPurchaseStore );
			if ( store && typeof store.getPurchasedLabels === 'function' ) {
				const purchasedLabels = store.getPurchasedLabels();
				const returnShipmentsFromLabels: Record<
					string,
					ReturnShipmentInfo
				> = {};

				// Extract return shipment info from purchased labels
				Object.entries( purchasedLabels ).forEach(
					( [ shipmentId, label ] ) => {
						if ( label?.isReturn ) {
							returnShipmentsFromLabels[ shipmentId ] = {
								isReturn: true,
								parentShipmentId: label.parentShipmentId,
							};
						}
					}
				);

				// Also check selected rates for any in-progress return shipments
				if ( typeof store.getSelectedRates === 'function' ) {
					const selectedRates = store.getSelectedRates();
					if ( selectedRates ) {
						Object.entries( selectedRates ).forEach(
							( [ shipmentKey, rateData ] ) => {
								if (
									rateData?.rate?.isReturn &&
									! returnShipmentsFromLabels[ shipmentKey ]
								) {
									// Only add from rates if not already in purchased labels
									returnShipmentsFromLabels[ shipmentKey ] = {
										isReturn: true,
									};
								}
							}
						);
					}
				}

				setReturnShipments( returnShipmentsFromLabels );
			}
		} catch {
			// Silently handle cases where the store or method doesn't exist (e.g., in tests)
		}
	}, [] );

	const getCurrentShipmentIsReturn = () => {
		return isReturnShipment( currentShipmentId );
	};

	const getCurrentShipmentParentId = () => {
		return returnShipments[ currentShipmentId ]?.parentShipmentId;
	};

	// Only set default dates if the current shipment doesn't have dates defined
	useEffect( () => {
		if (
			shipmentDates[ currentShipmentId ]?.shippingDate ||
			shipmentDates[ currentShipmentId ]?.estimatedDeliveryDate
		) {
			return;
		}
		setShipmentDates( ( prevState ) => ( {
			...prevState,
			[ currentShipmentId ]: getShipmentDefaultDates(
				currentShipmentId,
				activePurchasedLabel
			),
		} ) );
	}, [ currentShipmentId, shipmentDates, activePurchasedLabel ] );

	const getShipmentType = useCallback(
		( shipmentKey: string ): ShipmentType => {
			return isReturnShipment( shipmentKey )
				? SHIPMENT_TYPE.RETURN
				: SHIPMENT_TYPE.OUTBOUND;
		},
		[ isReturnShipment ]
	);

	return {
		shipments,
		setShipments,
		getShipmentWeight,
		resetSelections,
		selections,
		setSelection,
		currentShipmentId,
		setCurrentShipmentId,
		getShipmentItems,
		getSelectionItems,
		getShipmentOrigin,
		setShipmentOrigin,
		getShipmentDestination,
		revertLabelShipmentIdsToUpdate,
		labelShipmentIdsToUpdate,
		getShipmentPurchaseOrigin,
		hasVariations,
		hasMultipleShipments,
		isExtraLabelPurchaseValid,
		resetShipmentAndSelection,
		isShipmentAutogeneratedFromLabel,
		setCurrentShipmentDate,
		getCurrentShipmentDate,
		getCurrentShipmentIsReturn,
		getCurrentShipmentParentId,
		setReturnShipments,
		returnShipments,
		isReturnShipment,
		getShipmentType,
	};
}
