/* eslint-disable xstate/no-inline-implementation */
/**
 * External dependencies
 */
import { createMachine, assign, DoneInvokeEvent, actions, spawn } from 'xstate';
import { useMachine } from '@xstate/react';
import { useEffect, useMemo } from '@wordpress/element';
import { resolveSelect, dispatch } from '@wordpress/data';
import {
	ExtensionList,
	OPTIONS_STORE_NAME,
	COUNTRIES_STORE_NAME,
	Country,
	ONBOARDING_STORE_NAME,
	Extension,
	GeolocationResponse,
} from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
import { initializeExPlat } from '@woocommerce/explat';
import { CountryStateOption } from '@woocommerce/onboarding';

/**
 * Internal dependencies
 */
import { IntroOptIn } from './pages/IntroOptIn';
import {
	UserProfile,
	BusinessChoice,
	SellingOnlineAnswer,
	SellingPlatform,
} from './pages/UserProfile';
import {
	BusinessInfo,
	IndustryChoice,
	POSSIBLY_DEFAULT_STORE_NAMES,
} from './pages/BusinessInfo';
import { BusinessLocation } from './pages/BusinessLocation';
import { getCountryStateOptions } from './services/country';
import { Loader } from './pages/Loader';
import { Plugins } from './pages/Plugins';
import { getPluginTrackKey, getTimeFrame } from '~/utils';
import './style.scss';
import {
	InstallationCompletedResult,
	InstallAndActivatePlugins,
	InstalledPlugin,
	PluginInstallError,
} from './services/installAndActivatePlugins';
import { ProfileSpinner } from './components/profile-spinner/profile-spinner';
import recordTracksActions from './actions/tracks';

export type InitializationCompleteEvent = {
	type: 'INITIALIZATION_COMPLETE';
	payload: { optInDataSharing: boolean };
};

export type IntroOptInEvent =
	| { type: 'INTRO_COMPLETED'; payload: { optInDataSharing: boolean } } // can be true or false depending on whether the user opted in or not
	| { type: 'INTRO_SKIPPED'; payload: { optInDataSharing: false } }; // always false for now

export type UserProfileEvent =
	| {
			type: 'USER_PROFILE_COMPLETED';
			payload: {
				userProfile: CoreProfilerStateMachineContext[ 'userProfile' ];
			}; // TODO: fill in the types for this when developing this page
	  }
	| {
			type: 'USER_PROFILE_SKIPPED';
			payload: { userProfile: { skipped: true } };
	  };

export type BusinessInfoEvent = {
	type: 'BUSINESS_INFO_COMPLETED';
	payload: {
		storeName?: string;
		industry?: IndustryChoice;
		storeLocation: CountryStateOption[ 'key' ];
		geolocationOverruled: boolean;
	};
};

export type BusinessLocationEvent = {
	type: 'BUSINESS_LOCATION_COMPLETED';
	payload: {
		storeLocation: CountryStateOption[ 'key' ];
	};
};

export type PluginsInstallationRequestedEvent = {
	type: 'PLUGINS_INSTALLATION_REQUESTED';
	payload: {
		plugins: CoreProfilerStateMachineContext[ 'pluginsSelected' ];
	};
};

// TODO: add types as we develop the pages
export type OnboardingProfile = {
	business_choice: BusinessChoice;
	industry: Array< IndustryChoice >;
	selling_online_answer: SellingOnlineAnswer | null;
	selling_platforms: SellingPlatform[] | null;
	skip?: boolean;
	is_store_country_set: boolean | null;
};

export type PluginsPageSkippedEvent = {
	type: 'PLUGINS_PAGE_SKIPPED';
};

export type PluginInstalledAndActivatedEvent = {
	type: 'PLUGIN_INSTALLED_AND_ACTIVATED';
	payload: {
		pluginsCount: number;
		installedPluginIndex: number;
	};
};
export type PluginsInstallationCompletedEvent = {
	type: 'PLUGINS_INSTALLATION_COMPLETED';
	payload: {
		installationCompletedResult: InstallationCompletedResult;
	};
};

export type PluginsInstallationCompletedWithErrorsEvent = {
	type: 'PLUGINS_INSTALLATION_COMPLETED_WITH_ERRORS';
	payload: {
		errors: PluginInstallError[];
	};
};

export type CoreProfilerStateMachineContext = {
	optInDataSharing: boolean;
	userProfile: {
		businessChoice?: BusinessChoice;
		sellingOnlineAnswer?: SellingOnlineAnswer | null;
		sellingPlatforms?: SellingPlatform[] | null;
		skipped?: boolean;
	};
	pluginsAvailable: ExtensionList[ 'plugins' ] | [];
	pluginsSelected: string[]; // extension slugs
	pluginsInstallationErrors: PluginInstallError[];
	geolocatedLocation: GeolocationResponse | undefined;
	businessInfo: {
		storeName?: string | undefined;
		industry?: string | undefined;
		location?: string;
	};
	countries: CountryStateOption[];
	loader: {
		progress?: number;
		className?: string;
		useStages?: string;
		stageIndex?: number;
	};
	onboardingProfile: OnboardingProfile;
};

const getAllowTrackingOption = async () =>
	resolveSelect( OPTIONS_STORE_NAME ).getOption(
		'woocommerce_allow_tracking'
	);

const handleTrackingOption = assign( {
	optInDataSharing: (
		_context,
		event: DoneInvokeEvent< 'no' | 'yes' | undefined >
	) => event.data !== 'no',
} );

const getStoreNameOption = async () =>
	resolveSelect( OPTIONS_STORE_NAME ).getOption( 'blogname' );

const handleStoreNameOption = assign( {
	businessInfo: (
		context: CoreProfilerStateMachineContext,
		event: DoneInvokeEvent< string | undefined >
	) => {
		return {
			...context.businessInfo,
			storeName: POSSIBLY_DEFAULT_STORE_NAMES.includes( event.data ) // if its empty or the default, show empty to the user
				? undefined
				: event.data,
		};
	},
} );

const getStoreCountryOption = async () =>
	resolveSelect( OPTIONS_STORE_NAME ).getOption(
		'woocommerce_default_country'
	);

const handleStoreCountryOption = assign( {
	businessInfo: (
		context: CoreProfilerStateMachineContext,
		event: DoneInvokeEvent< string | undefined >
	) => {
		return {
			...context.businessInfo,
			location: event.data,
		};
	},
} );

/**
 * Prefetch it so that @wp/data caches it and there won't be a loading delay when its used
 */
const preFetchGetCountries = assign( {
	spawnGetCountriesRef: () =>
		spawn(
			() => resolveSelect( COUNTRIES_STORE_NAME ).getCountries(),
			'core-profiler-prefetch-countries'
		),
} );

const preFetchOptions = assign( {
	spawnPrefetchOptionsRef: ( _context, _event, { action } ) => {
		spawn(
			() =>
				Promise.all( [
					// @ts-expect-error -- not sure its possible to type this yet, maybe in xstate v5
					action.options.map( ( optionName: string ) =>
						resolveSelect( OPTIONS_STORE_NAME ).getOption(
							optionName
						)
					),
				] ),
			'core-profiler-prefetch-options'
		);
	},
} );

const getCountries = async () =>
	resolveSelect( COUNTRIES_STORE_NAME ).getCountries();

const handleCountries = assign( {
	countries: ( _context, event: DoneInvokeEvent< Country[] > ) => {
		return getCountryStateOptions( event.data );
	},
} );

const getOnboardingProfileOption = async () =>
	resolveSelect( OPTIONS_STORE_NAME ).getOption(
		'woocommerce_onboarding_profile'
	);

const handleOnboardingProfileOption = assign( {
	userProfile: (
		_context,
		event: DoneInvokeEvent< OnboardingProfile | undefined >
	) => {
		if ( ! event.data ) {
			return {};
		}

		const {
			business_choice: businessChoice,
			selling_online_answer: sellingOnlineAnswer,
			selling_platforms: sellingPlatforms,
			...rest
		} = event.data;
		return {
			...rest,
			businessChoice,
			sellingOnlineAnswer,
			sellingPlatforms,
		};
	},
} );

const assignOnboardingProfile = assign( {
	onboardingProfile: (
		_context,
		event: DoneInvokeEvent< OnboardingProfile | undefined >
	) => event.data,
} );

const getGeolocation = async ( context: CoreProfilerStateMachineContext ) => {
	if ( context.optInDataSharing ) {
		return resolveSelect( COUNTRIES_STORE_NAME ).geolocate();
	}
	return undefined;
};

const preFetchGeolocation = assign( {
	spawnGeolocationRef: ( context: CoreProfilerStateMachineContext ) =>
		spawn(
			() => getGeolocation( context ),
			'core-profiler-prefetch-geolocation'
		),
} );

const handleGeolocation = assign( {
	geolocatedLocation: (
		_context,
		event: DoneInvokeEvent< GeolocationResponse >
	) => {
		return event.data;
	},
} );

const redirectToWooHome = () => {
	/**
	 * @todo replace with navigateTo
	 */
	window.location.href = '/wp-admin/admin.php?page=wc-admin';
};

const updateTrackingOption = (
	_context: CoreProfilerStateMachineContext,
	event: IntroOptInEvent
) => {
	if (
		event.payload.optInDataSharing &&
		typeof window.wcTracks.enable === 'function'
	) {
		window.wcTracks.enable( () => {
			initializeExPlat();
		} );
	} else if ( ! event.payload.optInDataSharing ) {
		window.wcTracks.isEnabled = false;
	}

	const trackingValue = event.payload.optInDataSharing ? 'yes' : 'no';
	dispatch( OPTIONS_STORE_NAME ).updateOptions( {
		woocommerce_allow_tracking: trackingValue,
	} );
};

// TODO: move the data references over to the context.onboardingProfile object which stores the entire woocommerce_onboarding_profile contents
const updateOnboardingProfileOption = (
	context: CoreProfilerStateMachineContext
) => {
	const { businessChoice, sellingOnlineAnswer, sellingPlatforms } =
		context.userProfile;

	return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
		woocommerce_onboarding_profile: {
			...context.onboardingProfile,
			business_choice: businessChoice,
			selling_online_answer: sellingOnlineAnswer,
			selling_platforms: sellingPlatforms,
		},
	} );
};

const updateBusinessLocation = ( countryAndState: string ) => {
	return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
		woocommerce_default_country: countryAndState,
	} );
};

const updateBusinessInfo = async (
	_context: CoreProfilerStateMachineContext,
	event: BusinessInfoEvent
) => {
	const refreshedOnboardingProfile = ( await resolveSelect(
		OPTIONS_STORE_NAME
	).getOption( 'woocommerce_onboarding_profile' ) ) as OnboardingProfile;
	return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
		blogname: event.payload.storeName,
		woocommerce_default_country: event.payload.storeLocation,
		woocommerce_onboarding_profile: {
			...refreshedOnboardingProfile,
			is_store_country_set: true,
			industry: [ event.payload.industry ],
		},
	} );
};

const persistBusinessInfo = assign( {
	persistBusinessInfoRef: (
		context: CoreProfilerStateMachineContext,
		event: BusinessInfoEvent
	) =>
		spawn(
			() => updateBusinessInfo( context, event ),
			'core-profiler-update-business-info'
		),
} );

const promiseDelay = ( milliseconds: number ) => {
	return new Promise( ( resolve ) => {
		setTimeout( resolve, milliseconds );
	} );
};

/**
 * Assigns the optInDataSharing value from the event payload to the context
 */
const assignOptInDataSharing = assign( {
	optInDataSharing: ( _context, event: IntroOptInEvent ) =>
		event.payload.optInDataSharing,
} );

/**
 * Prefetch it so that @wp/data caches it and there won't be a loading delay when its used
 */
const preFetchGetPlugins = assign( {
	extensionsRef: () =>
		spawn(
			() => resolveSelect( ONBOARDING_STORE_NAME ).getFreeExtensions(),
			'core-profiler-prefetch-extensions'
		),
} );

const getPlugins = async () => {
	dispatch( ONBOARDING_STORE_NAME ).invalidateResolution(
		'getFreeExtensions'
	);
	const extensionsBundles = await resolveSelect(
		ONBOARDING_STORE_NAME
	).getFreeExtensions();
	return (
		extensionsBundles.find(
			( bundle ) => bundle.key === 'obw/core-profiler'
		)?.plugins || []
	);
};

const handlePlugins = assign( {
	pluginsAvailable: ( _context, event: DoneInvokeEvent< Extension[] > ) =>
		event.data,
} );

export const preFetchActions = {
	preFetchGetPlugins,
	preFetchGetCountries,
	preFetchGeolocation,
	preFetchOptions,
};

const coreProfilerMachineActions = {
	...preFetchActions,
	...recordTracksActions,
	handlePlugins,
	updateTrackingOption,
	handleTrackingOption,
	handleGeolocation,
	handleStoreNameOption,
	handleStoreCountryOption,
	assignOptInDataSharing,
	handleCountries,
	handleOnboardingProfileOption,
	assignOnboardingProfile,
	persistBusinessInfo,
	redirectToWooHome,
};

const coreProfilerMachineServices = {
	getAllowTrackingOption,
	getStoreNameOption,
	getStoreCountryOption,
	getCountries,
	getGeolocation,
	getOnboardingProfileOption,
	getPlugins,
};
export const coreProfilerStateMachineDefinition = createMachine( {
	id: 'coreProfiler',
	initial: 'initializing',
	predictableActionArguments: true, // recommended setting: https://xstate.js.org/docs/guides/actions.html
	context: {
		// these are safe default values if for some reason the steps fail to complete correctly
		// actual defaults displayed to the user should be handled in the steps themselves
		optInDataSharing: false,
		userProfile: { skipped: true },
		geolocatedLocation: undefined,
		businessInfo: {
			storeName: undefined,
			industry: undefined,
			storeCountryPreviouslySet: false,
			location: 'US:CA',
		},
		countries: [] as CountryStateOption[],
		pluginsAvailable: [],
		pluginsInstallationErrors: [],
		pluginsSelected: [],
		loader: {},
		onboardingProfile: {} as OnboardingProfile,
	} as CoreProfilerStateMachineContext,
	states: {
		initializing: {
			entry: [
				// these prefetch tasks are spawned actors in the background and do not block progression of the state machine
				'preFetchGetPlugins',
				'preFetchGetCountries',
				{
					type: 'preFetchOptions',
					options: [
						'blogname',
						'woocommerce_onboarding_profile',
						'woocommerce_default_country',
					],
				},
			],
			type: 'parallel',
			states: {
				// if we have any other init tasks to do in parallel, add them as a parallel state here.
				// this blocks the introOptIn UI from loading keep that in mind when adding new tasks here
				trackingOption: {
					initial: 'fetching',
					states: {
						fetching: {
							invoke: {
								src: 'getAllowTrackingOption',
								onDone: [
									{
										actions: [ 'handleTrackingOption' ],
										target: 'done',
									},
								],
								onError: {
									target: 'done', // leave it as initialised default on error
								},
							},
						},
						done: {
							type: 'final',
						},
					},
				},
			},
			onDone: {
				target: 'introOptIn',
				// TODO: at this point, we can handle the URL path param if any and jump to the correct page
			},
			meta: {
				progress: 0,
			},
		},
		introOptIn: {
			on: {
				INTRO_COMPLETED: {
					target: 'preUserProfile',
					actions: [
						'assignOptInDataSharing',
						'updateTrackingOption',
					],
				},
				INTRO_SKIPPED: {
					// if the user skips the intro, we set the optInDataSharing to false and go to the Business Location page
					target: 'preSkipFlowBusinessLocation',
					actions: [
						'assignOptInDataSharing',
						'updateTrackingOption',
					],
				},
			},
			entry: [
				{ type: 'recordTracksStepViewed', step: 'store_details' },
			],
			exit: actions.choose( [
				{
					cond: ( _context, event ) =>
						event.type === 'INTRO_COMPLETED',
					actions: 'recordTracksIntroCompleted',
				},
				{
					cond: ( _context, event ) => event.type === 'INTRO_SKIPPED',
					actions: [
						{
							type: 'recordTracksStepSkipped',
							step: 'store_details',
						},
					],
				},
			] ),
			meta: {
				progress: 20,
				component: IntroOptIn,
			},
		},
		preUserProfile: {
			invoke: {
				src: 'getOnboardingProfileOption',
				onDone: [
					{
						actions: [
							'handleOnboardingProfileOption',
							'assignOnboardingProfile',
						],
						target: 'userProfile',
					},
				],
				onError: {
					target: 'userProfile',
				},
			},
		},
		userProfile: {
			entry: [
				{ type: 'recordTracksStepViewed', step: 'user_profile' },
				'preFetchGeolocation',
			],
			on: {
				USER_PROFILE_COMPLETED: {
					target: 'postUserProfile',
					actions: [
						assign( {
							userProfile: ( context, event: UserProfileEvent ) =>
								event.payload.userProfile, // sets context.userProfile to the payload of the event
						} ),
					],
				},
				USER_PROFILE_SKIPPED: {
					target: 'postUserProfile',
					actions: [
						assign( {
							userProfile: ( context, event: UserProfileEvent ) =>
								event.payload.userProfile, // assign context.userProfile to the payload of the event
						} ),
					],
				},
			},
			exit: actions.choose( [
				{
					cond: ( _context, event ) =>
						event.type === 'USER_PROFILE_COMPLETED',
					actions: 'recordTracksUserProfileCompleted',
				},
				{
					cond: ( _context, event ) =>
						event.type === 'USER_PROFILE_SKIPPED',
					actions: [
						{
							type: 'recordTracksStepSkipped',
							step: 'user_profile',
						},
					],
				},
			] ),
			meta: {
				progress: 40,
				component: UserProfile,
			},
		},
		postUserProfile: {
			invoke: {
				src: ( context ) => {
					return updateOnboardingProfileOption( context );
				},
				onDone: {
					target: 'preBusinessInfo',
				},
				onError: {
					target: 'preBusinessInfo',
				},
			},
		},
		preBusinessInfo: {
			type: 'parallel',
			states: {
				geolocation: {
					initial: 'checkDataOptIn',
					states: {
						checkDataOptIn: {
							// if the user has opted out of data sharing, we skip the geolocation step
							always: [
								{
									cond: ( context ) =>
										context.optInDataSharing,
									target: 'fetching',
								},
								{
									target: 'done',
								},
							],
						},
						fetching: {
							invoke: {
								src: 'getGeolocation',
								onDone: {
									target: 'done',
									actions: 'handleGeolocation',
								},
								// onError TODO: handle error
							},
						},
						done: {
							type: 'final',
						},
					},
				},
				storeCountryOption: {
					initial: 'fetching',
					states: {
						fetching: {
							invoke: {
								src: 'getStoreCountryOption',
								onDone: [
									{
										actions: [ 'handleStoreCountryOption' ],
										target: 'done',
									},
								],
								onError: {
									target: 'done',
								},
							},
						},
						done: {
							type: 'final',
						},
					},
				},
				storeNameOption: {
					initial: 'fetching',
					states: {
						fetching: {
							invoke: {
								src: 'getStoreNameOption',
								onDone: [
									{
										actions: [ 'handleStoreNameOption' ],
										target: 'done',
									},
								],
								onError: {
									target: 'done', // leave it as initialised default on error
								},
							},
						},
						done: {
							type: 'final',
						},
					},
				},
				countries: {
					initial: 'fetching',
					states: {
						fetching: {
							invoke: {
								src: 'getCountries',
								onDone: {
									target: 'done',
									actions: 'handleCountries',
								},
							},
						},
						done: {
							type: 'final',
						},
					},
				},
			},
			// onDone is reached when child parallel states are all at their final states
			onDone: {
				target: 'businessInfo',
			},
			meta: {
				progress: 50,
			},
		},
		businessInfo: {
			entry: [
				{ type: 'recordTracksStepViewed', step: 'business_info' },
			],
			on: {
				BUSINESS_INFO_COMPLETED: {
					target: 'prePlugins',
					actions: [
						'persistBusinessInfo',
						'recordTracksBusinessInfoCompleted',
					],
				},
			},
			meta: {
				progress: 60,
				component: BusinessInfo,
			},
		},
		preSkipFlowBusinessLocation: {
			invoke: {
				src: 'getCountries',
				onDone: [
					{
						actions: [ 'handleCountries' ],
						target: 'skipFlowBusinessLocation',
					},
				],
				onError: {
					target: 'skipFlowBusinessLocation',
				},
			},
		},
		skipFlowBusinessLocation: {
			on: {
				BUSINESS_LOCATION_COMPLETED: {
					target: 'postSkipFlowBusinessLocation',
					actions: [
						assign( {
							businessInfo: (
								context,
								event: BusinessLocationEvent
							) => {
								return {
									...context.businessInfo,
									location: event.payload.storeLocation,
								};
							},
						} ),
						'recordTracksSkipBusinessLocationCompleted',
					],
				},
			},
			entry: [
				{
					type: 'recordTracksStepViewed',
					step: 'skip_business_location',
				},
			],
			meta: {
				progress: 80,
				component: BusinessLocation,
			},
		},
		postSkipFlowBusinessLocation: {
			initial: 'updateBusinessLocation',
			states: {
				updateBusinessLocation: {
					entry: assign( {
						loader: {
							progress: 10,
						},
					} ),
					invoke: {
						src: ( context ) => {
							return updateBusinessLocation(
								context.businessInfo.location as string
							);
						},
						onDone: {
							target: 'progress20',
						},
					},
				},
				// Although we don't need to wait 3 seconds for the following states
				// We will dispaly 20% and 80% progress for 1.5 seconds each
				// for the sake of user experience.
				progress20: {
					entry: assign( {
						loader: {
							progress: 20,
						},
					} ),
					invoke: {
						src: () => {
							return promiseDelay( 1500 );
						},
						onDone: {
							target: 'progress80',
						},
					},
				},
				progress80: {
					entry: assign( {
						loader: {
							progress: 80,
						},
					} ),
					invoke: {
						src: () => {
							return promiseDelay( 1500 );
						},
						onDone: {
							actions: [ 'redirectToWooHome' ],
						},
					},
				},
			},
			meta: {
				component: Loader,
			},
		},
		prePlugins: {
			invoke: {
				src: 'getPlugins',
				onDone: [
					{
						target: 'pluginsSkipped',
						cond: ( context, event ) => event.data.length === 0,
					},
					{ target: 'plugins', actions: 'handlePlugins' },
				],
			},
			// add exit action to filter the extensions using a custom function here and assign it to context.extensionsAvailable
			exit: assign( {
				pluginsAvailable: ( context ) => {
					return context.pluginsAvailable.filter( () => true );
				}, // TODO : define an extensible filter function here
			} ),
			meta: {
				progress: 70,
			},
		},
		pluginsSkipped: {
			entry: assign( {
				loader: {
					progress: 80,
				},
			} ),
			invoke: {
				src: () => {
					dispatch( ONBOARDING_STORE_NAME ).updateProfileItems( {
						plugins_page_skipped: true,
						completed: true,
					} );
					return promiseDelay( 3000 );
				},
				onDone: {
					actions: [ 'redirectToWooHome' ],
				},
			},
			meta: {
				component: Loader,
			},
		},
		plugins: {
			entry: [ { type: 'recordTracksStepViewed', step: 'plugins' } ],
			on: {
				PLUGINS_PAGE_SKIPPED: {
					actions: [
						{
							type: 'recordTracksStepSkipped',
							step: 'plugins',
						},
					],
					target: 'pluginsSkipped',
				},
				PLUGINS_INSTALLATION_REQUESTED: {
					target: 'installPlugins',
					actions: [
						assign( {
							pluginsSelected: (
								_context,
								event: PluginsInstallationRequestedEvent
							) => event.payload.plugins,
						} ),
					],
				},
			},
			meta: {
				progress: 80,
				component: Plugins,
			},
		},
		postPluginInstallation: {
			invoke: {
				src: async ( _context, event ) => {
					return await dispatch(
						ONBOARDING_STORE_NAME
					).updateProfileItems( {
						business_extensions:
							event.payload.installationCompletedResult.installedPlugins.map(
								( extension: InstalledPlugin ) =>
									extension.plugin
							),
						completed: true,
					} );
				},
				onDone: {
					actions: 'redirectToWooHome',
				},
			},
			meta: {
				component: Loader,
				progress: 100,
			},
		},
		installPlugins: {
			on: {
				PLUGIN_INSTALLED_AND_ACTIVATED: {
					actions: [
						assign( {
							loader: (
								_context,
								event: PluginInstalledAndActivatedEvent
							) => {
								const progress = Math.round(
									( event.payload.installedPluginIndex /
										event.payload.pluginsCount ) *
										100
								);

								let stageIndex = 0;

								if ( progress > 30 ) {
									stageIndex = 1;
								} else if ( progress > 60 ) {
									stageIndex = 2;
								}

								return {
									useStages: 'plugins',
									progress,
									stageIndex,
								};
							},
						} ),
					],
				},
				PLUGINS_INSTALLATION_COMPLETED_WITH_ERRORS: {
					target: 'prePlugins',
					actions: [
						assign( {
							pluginsInstallationErrors: ( _context, event ) =>
								event.payload.errors,
						} ),
						( _context, event ) => {
							recordEvent(
								'storeprofiler_store_extensions_installed_and_activated',
								{
									success: false,
									failed_extensions: event.payload.errors.map(
										( error: PluginInstallError ) =>
											getPluginTrackKey( error.plugin )
									),
								}
							);
						},
					],
				},
				PLUGINS_INSTALLATION_COMPLETED: {
					target: 'postPluginInstallation',
					actions: [
						( _context, event ) => {
							const installationCompletedResult =
								event.payload.installationCompletedResult;

							const trackData: {
								success: boolean;
								installed_extensions: string[];
								total_time: string;
								[ key: string ]:
									| number
									| boolean
									| string
									| string[];
							} = {
								success: true,
								installed_extensions:
									installationCompletedResult.installedPlugins.map(
										( installedPlugin: InstalledPlugin ) =>
											getPluginTrackKey(
												installedPlugin.plugin
											)
									),
								total_time: getTimeFrame(
									installationCompletedResult.totalTime
								),
							};

							for ( const installedPlugin of installationCompletedResult.installedPlugins ) {
								trackData[
									'install_time_' +
										getPluginTrackKey(
											installedPlugin.plugin
										)
								] = getTimeFrame( installedPlugin.installTime );
							}

							recordEvent(
								'storeprofiler_store_extensions_installed_and_activated',
								trackData
							);
						},
					],
				},
			},
			entry: [
				assign( {
					loader: {
						progress: 10,
						useStages: 'plugins',
					},
				} ),
			],
			invoke: {
				src: InstallAndActivatePlugins,
			},
			meta: {
				component: Loader,
			},
		},
		settingUpStore: {},
	},
} );

export const CoreProfilerController = ( {
	actionOverrides,
	servicesOverrides,
}: {
	actionOverrides: Partial< typeof coreProfilerMachineActions >;
	servicesOverrides: Partial< typeof coreProfilerMachineServices >;
} ) => {
	const augmentedStateMachine = useMemo( () => {
		// When adding extensibility, this is the place to manipulate the state machine definition.
		return coreProfilerStateMachineDefinition.withConfig( {
			actions: {
				...coreProfilerMachineActions,
				...actionOverrides,
			},
			services: {
				...coreProfilerMachineServices,
				...servicesOverrides,
			},
		} );
	}, [ actionOverrides, servicesOverrides ] );

	const [ state, send ] = useMachine( augmentedStateMachine, {
		devTools: process.env.NODE_ENV === 'development',
	} );
	const stateValue =
		typeof state.value === 'object'
			? Object.keys( state.value )[ 0 ]
			: state.value;
	const currentNodeMeta = state.meta[ `coreProfiler.${ stateValue }` ]
		? state.meta[ `coreProfiler.${ stateValue }` ]
		: undefined;
	const navigationProgress = currentNodeMeta?.progress; // This value is defined in each state node's meta tag, we can assume it is 0-100
	const CurrentComponent =
		currentNodeMeta?.component ?? ( () => <ProfileSpinner /> ); // If no component is defined for the state then its a loading state

	useEffect( () => {
		document.body.classList.remove( 'woocommerce-admin-is-loading' );
		document.body.classList.add( 'woocommerce-profile-wizard__body' );
		document.body.classList.add( 'woocommerce-admin-full-screen' );
		document.body.classList.add( 'is-wp-toolbar-disabled' );
		return () => {
			document.body.classList.remove(
				'woocommerce-profile-wizard__body'
			);
			document.body.classList.remove( 'woocommerce-admin-full-screen' );
			document.body.classList.remove( 'is-wp-toolbar-disabled' );
		};
	} );

	return (
		<>
			<div
				className={ `woocommerce-profile-wizard__container woocommerce-profile-wizard__step-${ state.value }` }
			>
				{
					<CurrentComponent
						navigationProgress={ navigationProgress }
						sendEvent={ send }
						context={ state.context }
					/>
				}
			</div>
		</>
	);
};
export default CoreProfilerController;
