/**
 * External dependencies
 */
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import {
	Activity,
	Children,
	cloneElement,
	Component,
	createContext,
	createElement,
	createRef,
	forwardRef,
	Fragment,
	isValidElement,
	memo,
	PureComponent,
	StrictMode,
	use,
	useActionState,
	useCallback,
	useContext,
	useDebugValue,
	useDeferredValue,
	useEffect,
	useEffectEvent,
	useId,
	useMemo,
	useImperativeHandle,
	useInsertionEffect,
	useLayoutEffect,
	useOptimistic,
	useReducer,
	useRef,
	useState,
	useSyncExternalStore,
	useTransition,
	startTransition,
	lazy,
	Suspense,
} from 'react';
import type { ReactNode } from 'react';

/**
 * Object containing a React element.
 */
export type Element = React.ReactElement;

/**
 * Object containing a React component.
 */
export type ComponentType< T = any > = React.ComponentType< T >;

/**
 * Object containing a React synthetic event.
 */
export type SyntheticEvent< T = Element > = React.SyntheticEvent< T >;

/**
 * Object containing a React ref object.
 */
export type RefObject< T > = React.RefObject< T >;

/**
 * Object containing a React ref callback.
 */
export type RefCallback< T > = React.RefCallback< T >;

/**
 * Object containing a React ref.
 */
export type Ref< T > = React.Ref< T >;

/**
 * Hide and show parts of the UI while preserving state.
 *
 * @since 7.1.0
 * @see https://react.dev/reference/react/Activity
 */
export { Activity };

/**
 * Object that provides utilities for dealing with React children.
 */
export { Children };

/**
 * Creates a copy of an element with extended props.
 *
 * @param {Element} element Element
 * @param {?Object} props   Props to apply to cloned element
 *
 * @return {Element} Cloned element.
 */
export { cloneElement };

/**
 * A base class to create WordPress Components (Refs, state and lifecycle hooks)
 */
export { Component };

/**
 * Creates a context object containing two components: a provider and consumer.
 *
 * @param {Object} defaultValue A default data stored in the context.
 *
 * @return {Object} Context object.
 */
export { createContext };

/**
 * Returns a new element of given type. Type can be either a string tag name or
 * another function which itself returns an element.
 *
 * @param {?(string|Function)} type     Tag name or element creator
 * @param {Object}             props    Element properties, either attribute
 *                                      set to apply to DOM node or values to
 *                                      pass through to element creator
 * @param {...Element}         children Descendant elements
 *
 * @return {Element} Element.
 */
export { createElement };

/**
 * Returns an object tracking a reference to a rendered element via its
 * `current` property as either a DOMElement or Element, dependent upon the
 * type of element rendered with the ref attribute.
 *
 * @return {Object} Ref object.
 */
export { createRef };

/**
 * Component enhancer used to enable passing a ref to its wrapped component.
 * Pass a function argument which receives `props` and `ref` as its arguments,
 * returning an element using the forwarded ref. The return value is a new
 * component which forwards its ref.
 *
 * @param {Function} forwarder Function passed `props` and `ref`, expected to
 *                             return an element.
 *
 * @return {Component} Enhanced component.
 */
export { forwardRef };

/**
 * A component which renders its children without any wrapping element.
 */
export { Fragment };

/**
 * Checks if an object is a valid React Element.
 *
 * @param {Object} objectToCheck The object to be checked.
 *
 * @return {boolean} true if objectToTest is a valid React Element and false otherwise.
 */
export { isValidElement };

/**
 * @see https://react.dev/reference/react/memo
 */
export { memo };

/**
 * Component that activates additional checks and warnings for its descendants.
 */
export { StrictMode };

/**
 * Read the value of a resource like a Promise or context.
 *
 * @since 7.1.0
 * @see https://react.dev/reference/react/use
 */
export { use };

/**
 * Manage state based on the result of a form action.
 *
 * @since 7.1.0
 * @see https://react.dev/reference/react/useActionState
 */
export { useActionState };

/**
 * @see https://react.dev/reference/react/useCallback
 */
export { useCallback };

/**
 * @see https://react.dev/reference/react/useContext
 */
export { useContext };

/**
 * @see https://react.dev/reference/react/useDebugValue
 */
export { useDebugValue };

/**
 * @see https://react.dev/reference/react/useDeferredValue
 */
export { useDeferredValue };

/**
 * @see https://react.dev/reference/react/useEffect
 */
export { useEffect };

/**
 * Extract non-reactive logic from an Effect into an Effect Event.
 *
 * @since 7.1.0
 * @see https://react.dev/reference/react/useEffectEvent
 */
export { useEffectEvent };

/**
 * @see https://react.dev/reference/react/useId
 */
export { useId };

/**
 * @see https://react.dev/reference/react/useImperativeHandle
 */
export { useImperativeHandle };

/**
 * @see https://react.dev/reference/react/useInsertionEffect
 */
export { useInsertionEffect };

/**
 * @see https://react.dev/reference/react/useLayoutEffect
 */
export { useLayoutEffect };

/**
 * @see https://react.dev/reference/react/useMemo
 */
export { useMemo };

/**
 * Show a different state while an async action is underway.
 *
 * @since 7.1.0
 * @see https://react.dev/reference/react/useOptimistic
 */
export { useOptimistic };

/**
 * @see https://react.dev/reference/react/useReducer
 */
export { useReducer };

/**
 * @see https://react.dev/reference/react/useRef
 */
export { useRef };

/**
 * @see https://react.dev/reference/react/useState
 */
export { useState };

/**
 * @see https://react.dev/reference/react/useSyncExternalStore
 */
export { useSyncExternalStore };

/**
 * @see https://react.dev/reference/react/useTransition
 */
export { useTransition };

/**
 * @see https://react.dev/reference/react/startTransition
 */
export { startTransition };

/**
 * @see https://react.dev/reference/react/lazy
 */
export { lazy };

/**
 * @see https://react.dev/reference/react/Suspense
 */
export { Suspense };

/**
 * @see https://react.dev/reference/react/PureComponent
 */
export { PureComponent };

/**
 * Concatenate two or more React children objects.
 *
 * @param childrenArguments - Array of children arguments (array of arrays/strings/objects) to concatenate.
 * @return The concatenated value.
 */
export function concatChildren(
	...childrenArguments: ReactNode[][]
): ReactNode[] {
	return childrenArguments.reduce< ReactNode[] >(
		( accumulator, children, i ) => {
			Children.forEach( children, ( child, j ) => {
				if ( isValidElement( child ) && typeof child !== 'string' ) {
					child = cloneElement( child, {
						key: [ i, j ].join(),
					} );
				}

				accumulator.push( child );
			} );

			return accumulator;
		},
		[]
	);
}

/**
 * Switches the nodeName of all the elements in the children object.
 *
 * @param children Children object.
 * @param nodeName Node name.
 *
 * @return  The updated children object.
 */
export function switchChildrenNodeName(
	children: ReactNode,
	nodeName: string
): ReactNode {
	return (
		children &&
		Children.map( children, ( elt, index ) => {
			if ( typeof elt?.valueOf() === 'string' ) {
				return createElement( nodeName, { key: index }, elt );
			}
			if ( ! isValidElement( elt ) ) {
				return elt;
			}

			const { children: childrenProp, ...props } = (
				elt as React.ReactElement< React.PropsWithChildren< unknown > >
			 ).props;
			return createElement(
				nodeName,
				{ key: index, ...props },
				childrenProp
			);
		} )
	);
}
