import React from "react";
import { useToast } from "@chakra-ui/react";
import { useLocation, useNavigate } from "react-router-dom";
import { useDebounce } from "../util";
import useCurrentUser from "../components/useCurrentUser";
import { outfitStatesAreEqual } from "./useOutfitState";
import { useSaveOutfitMutation } from "../loaders/outfits";

function useOutfitSaving(outfitState, dispatchToOutfit) {
	const { isLoggedIn, id: currentUserId } = useCurrentUser();
	const { pathname } = useLocation();
	const navigate = useNavigate();
	const toast = useToast();

	// Whether this outfit is new, i.e. local-only, i.e. has _never_ been saved
	// to the server.
	const isNewOutfit = outfitState.id == null;

	// Whether this outfit's latest local changes have been saved to the server.
	// And log it to the console!
	const latestVersionIsSaved =
		outfitState.savedOutfitState &&
		outfitStatesAreEqual(
			outfitState.outfitStateWithoutExtras,
			outfitState.savedOutfitState,
		);
	React.useEffect(() => {
		console.debug(
			"[useOutfitSaving] Latest version is saved? %s\nCurrent: %o\nSaved: %o",
			latestVersionIsSaved,
			outfitState.outfitStateWithoutExtras,
			outfitState.savedOutfitState,
		);
	}, [
		latestVersionIsSaved,
		outfitState.outfitStateWithoutExtras,
		outfitState.savedOutfitState,
	]);

	// Only logged-in users can save outfits - and they can only save new outfits,
	// or outfits they created.
	const canSaveOutfit =
		isLoggedIn && (isNewOutfit || outfitState.creator?.id === currentUserId);

	// Users can delete their own outfits too. The logic is slightly different
	// than for saving, because you can save an outfit that hasn't been saved
	// yet, but you can't delete it.
	const canDeleteOutfit = !isNewOutfit && canSaveOutfit;

	const saveOutfitMutation = useSaveOutfitMutation({
		onSuccess: (outfit) => {
			dispatchToOutfit({
				type: "handleOutfitSaveResponse",
				outfitData: outfit,
			});
		},
	});
	const isSaving = saveOutfitMutation.isPending;
	const saveError = saveOutfitMutation.error;

	const saveOutfitFromProvidedState = React.useCallback(
		(outfitState) => {
			saveOutfitMutation
				.mutateAsync({
					id: outfitState.id,
					name: outfitState.name,
					speciesId: outfitState.speciesId,
					colorId: outfitState.colorId,
					pose: outfitState.pose,
					appearanceId: outfitState.appearanceId,
					altStyleId: outfitState.altStyleId,
					wornItemIds: [...outfitState.wornItemIds],
					closetedItemIds: [...outfitState.closetedItemIds],
				})
				.then((outfit) => {
					// Navigate to the new saved outfit URL. Our Apollo cache should pick
					// up the data from this mutation response, and combine it with the
					// existing cached data, to make this smooth without any loading UI.
					if (pathname !== `/outfits/[outfitId]`) {
						navigate(`/outfits/${outfit.id}`);
					}
				})
				.catch((e) => {
					console.error(e);
					toast({
						status: "error",
						title: "Sorry, there was an error saving this outfit!",
						description: "Maybe check your connection and try again.",
					});
				});
		},
		// It's important that this callback _doesn't_ change when the outfit
		// changes, so that the auto-save effect is only responding to the
		// debounced state!
		[saveOutfitMutation, pathname, navigate, toast],
	);

	const saveOutfit = React.useCallback(
		() => saveOutfitFromProvidedState(outfitState.outfitStateWithoutExtras),
		[saveOutfitFromProvidedState, outfitState.outfitStateWithoutExtras],
	);

	// Auto-saving! First, debounce the outfit state. Use `outfitStateWithoutExtras`,
	// which only contains the basic fields, and will keep a stable object
	// identity until actual changes occur. Then, save the outfit after the user
	// has left it alone for long enough, so long as it's actually different
	// than the saved state.
	const debouncedOutfitState = useDebounce(
		outfitState.outfitStateWithoutExtras,
		2000,
		{
			// When the outfit ID changes, update the debounced state immediately!
			forceReset: (debouncedOutfitState, newOutfitState) =>
				debouncedOutfitState.id !== newOutfitState.id,
		},
	);
	// HACK: This prevents us from auto-saving the outfit state that's still
	//       loading. I worry that this might not catch other loading scenarios
	//       though, like if the species/color/pose is in the GQL cache, but the
	//       items are still loading in... not sure where this would happen tho!
	const debouncedOutfitStateIsSaveable =
		debouncedOutfitState.speciesId &&
		debouncedOutfitState.colorId &&
		debouncedOutfitState.pose;
	React.useEffect(() => {
		if (
			!isNewOutfit &&
			canSaveOutfit &&
			!isSaving &&
			!saveError &&
			debouncedOutfitStateIsSaveable &&
			!outfitStatesAreEqual(debouncedOutfitState, outfitState.savedOutfitState)
		) {
			console.info(
				"[useOutfitSaving] Auto-saving outfit\nSaved: %o\nCurrent (debounced): %o",
				outfitState.savedOutfitState,
				debouncedOutfitState,
			);
			saveOutfitFromProvidedState(debouncedOutfitState);
		}
	}, [
		isNewOutfit,
		canSaveOutfit,
		isSaving,
		saveError,
		debouncedOutfitState,
		debouncedOutfitStateIsSaveable,
		outfitState.savedOutfitState,
		saveOutfitFromProvidedState,
	]);

	// When the outfit changes, clear out the error state from previous saves.
	// We'll send the mutation again after the debounce, and we don't want to
	// show the error UI in the meantime!
	const resetMutation = saveOutfitMutation.reset;
	React.useEffect(
		() => resetMutation(),
		[outfitState.outfitStateWithoutExtras, resetMutation],
	);

	return {
		canSaveOutfit,
		canDeleteOutfit,
		isNewOutfit,
		isSaving,
		latestVersionIsSaved,
		saveError,
		saveOutfit,
	};
}

export default useOutfitSaving;