import { createReduxStore, register, createSelector } from '@wordpress/data';

// Overridable via the host's bootstrap config so two cliq-ui-based plugins can
// register distinct @wordpress/data stores without colliding. Resolved once at
// module load; every importer shares this value.
export const STORE_KEY =
	( typeof window !== 'undefined' && window.CLIQTHEMES_UI?.app?.storeKey ) ||
	'cliqthemes/ui/app';

const sanitizeIdentifier = ( value ) => {
	if ( ! value ) {
		return '';
	}

	const stringValue = value.toString();
	const slug = stringValue
		.trim()
		.toLowerCase()
		.normalize( 'NFKD' )
		.replace( /[\u0300-\u036f]/g, '' )
		.replace( /[^a-z0-9-]+/g, '-' )
		.replace( /-{2,}/g, '-' )
		.replace( /^-|-$/g, '' );

	if ( slug ) {
		return slug;
	}

	let hash = 0;
	for ( let i = 0; i < stringValue.length; i += 1 ) {
		hash = ( hash * 31 + stringValue.charCodeAt( i ) ) % 1000000007;
	}

	return `provider-${ Math.abs( hash ).toString( 36 ) }`;
};

const DEFAULT_STATE = {
	providers: {},
	order: [],
	selected: null,
	records: {},
	status: {},
	errors: {},
	pagination: {},
	rest: {
		namespace: '',
		root: '',
		nonce: '',
	},
};

const types = {
	BOOTSTRAP: 'BOOTSTRAP',
	SELECT_PROVIDER: 'SELECT_PROVIDER',
	SET_RECORDS: 'SET_RECORDS',
	SET_STATUS: 'SET_STATUS',
	SET_ERROR: 'SET_ERROR',
	UPDATE_PROVIDER: 'UPDATE_PROVIDER',
	SET_PAGINATION: 'SET_PAGINATION',
};

const recordsFromIterable = ( records ) => {
	if ( Array.isArray( records ) ) {
		return records;
	}

	if ( records && typeof records[ Symbol.iterator ] === 'function' ) {
		return Array.from( records );
	}

	return [];
};

const actions = {
	bootstrap( payload ) {
		return {
			type: types.BOOTSTRAP,
			payload,
		};
	},
	selectProvider( providerId ) {
		return {
			type: types.SELECT_PROVIDER,
			providerId,
		};
	},
	setRecords( providerId, records ) {
		return {
			type: types.SET_RECORDS,
			providerId,
			records,
		};
	},
	setStatus( providerId, status ) {
		return {
			type: types.SET_STATUS,
			providerId,
			status,
		};
	},
	setError( providerId, error ) {
		return {
			type: types.SET_ERROR,
			providerId,
			error,
		};
	},
	updateProvider( providerId, data ) {
		return {
			type: types.UPDATE_PROVIDER,
			providerId,
			data,
		};
	},
	setPagination( providerId, pagination ) {
		return {
			type: types.SET_PAGINATION,
			providerId,
			pagination,
		};
	},
};

const reducer = ( state = DEFAULT_STATE, action ) => {
	switch ( action.type ) {
		case types.BOOTSTRAP: {
			const payload = action.payload ?? {};
			const providersInput = payload.providers ?? {};
			const normalizedProviders = {};
			const order = [];
			const records = {};
			const status = {};
			const pagination = {};

			Object.entries( providersInput ).forEach(
				( [ id, definition ] ) => {
					const providerId = id.toString();
					const { records: initialRecords = [], ...rest } =
						definition ?? {};
					const route = sanitizeIdentifier( providerId );
					normalizedProviders[ providerId ] = {
						id: providerId,
						...rest,
						route,
						label:
							rest?.label ??
							providerId
								.replace( /[-_]/g, ' ' )
								.replace( /\b\w/g, ( char ) =>
									char.toUpperCase()
								),
					};
					order.push( providerId );
					const normalisedRecords =
						recordsFromIterable( initialRecords );
					records[ providerId ] = [];
					status[ providerId ] = 'idle';
					const configuredPerPage = Number( rest?.perPage ?? 20 );
					const initialPerPage =
						Number.isFinite( configuredPerPage ) &&
						configuredPerPage > 0
							? configuredPerPage
							: 20;
					const initialTotalPages = Math.max(
						1,
						Math.ceil(
							( normalisedRecords.length || 0 ) / initialPerPage
						)
					);
					pagination[ providerId ] = {
						total: normalisedRecords.length,
						per_page: initialPerPage,
						page: 1,
						total_pages: initialTotalPages,
					};
				}
			);

			const restConfig = {
				namespace: payload.rest?.namespace ?? state.rest.namespace,
				root: payload.rest?.root ?? state.rest.root,
				nonce: payload.rest?.nonce ?? state.rest.nonce,
				endpoint: payload.rest?.endpoint ?? payload.rest?.base ?? '',
			};

			return {
				...state,
				providers: normalizedProviders,
				order,
				selected: state.selected ?? order[ 0 ] ?? null,
				records,
				status,
				pagination: {
					...state.pagination,
					...pagination,
				},
				errors: {},
				rest: restConfig,
			};
		}
		case types.SELECT_PROVIDER:
			return {
				...state,
				selected: action.providerId,
			};
		case types.SET_RECORDS:
			return {
				...state,
				records: {
					...state.records,
					[ action.providerId ]: recordsFromIterable(
						action.records
					),
				},
			};
		case types.SET_STATUS:
			return {
				...state,
				status: {
					...state.status,
					[ action.providerId ]: action.status,
				},
			};
		case types.SET_ERROR: {
			const nextErrors = {
				...state.errors,
				[ action.providerId ]: action.error ?? null,
			};

			return {
				...state,
				errors: nextErrors,
			};
		}
		case types.UPDATE_PROVIDER: {
			if ( ! state.providers[ action.providerId ] ) {
				return state;
			}

			return {
				...state,
				providers: {
					...state.providers,
					[ action.providerId ]: {
						...state.providers[ action.providerId ],
						...( action.data ?? {} ),
					},
				},
			};
		}
		case types.SET_PAGINATION:
			return {
				...state,
				pagination: {
					...state.pagination,
					[ action.providerId ]: {
						total: action.pagination?.total ?? 0,
						per_page: action.pagination?.per_page ?? 20,
						page: action.pagination?.page ?? 1,
						total_pages: action.pagination?.total_pages ?? 1,
					},
				},
			};
		default:
			return state;
	}
};

const selectors = {
	getProviderIds: ( state ) => state.order,
	getProviders: createSelector(
		( state ) => state.order.map( ( id ) => state.providers[ id ] ),
		( state ) => [ state.order, state.providers ]
	),
	getProvider: ( state, providerId ) => {
		if ( ! providerId ) {
			return null;
		}

		return state.providers[ providerId ] ?? null;
	},
	getSelectedProviderId: ( state ) => state.selected,
	getRecords: ( state, providerId ) => state.records[ providerId ] ?? [],
	getStatus: ( state, providerId ) => state.status[ providerId ] ?? 'idle',
	getError: ( state, providerId ) => state.errors[ providerId ] ?? null,
	getRestConfig: ( state ) => state.rest,
	getPagination: ( state, providerId ) =>
		state.pagination[ providerId ] ?? {
			total: 0,
			per_page: 20,
			page: 1,
			total_pages: 1,
		},
};

const store = createReduxStore( STORE_KEY, {
	reducer,
	actions,
	selectors,
} );

register( store );

export const storeActions = store.actions;
export const storeSelectors = store.selectors;
export default store;
