import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses } from '@gravityforms/utils';

const { forwardRef } = React;

/**
 * @module Grid
 * @description A grid component in react.
 *
 * @since 1.1.18
 *
 * @param {object}                     props                  Component props.
 * @param {string}                     props.alignItems       The `align-items` CSS property, only applies if grid is container.
 * @param {JSX.Element|null}           props.children         The React children to contain in the grid component.
 * @param {number}                     props.columns          Number of grid columns. Enables CSS Grid mode when set.
 * @param {number}                     props.columnSpacing    The column spacing between grid items.
 * @param {boolean}                    props.container        Whether the grid component is a container or not.
 * @param {object}                     props.customAttributes Custom attributes for the component.
 * @param {string|Array|object}        props.customClasses    Custom classes for the component.
 * @param {string}                     props.direction        The `flex-direction` CSS property, only applies if grid is container.
 * @param {string}                     props.elementType      The element type, one of `list` or `div`.
 * @param {number}                     props.gap              Gap spacing using design token spacers (2, 3, 4, 5, 6, 8). Best used with CSS Grid mode.
 * @param {boolean}                    props.item             Whether the grid component is an item or not.
 * @param {string}                     props.justifyContent   The `justify-content` CSS property, only applies if grid is container.
 * @param {number}                     props.rowSpacing       The row spacing between grid items.
 * @param {string|number|Array|object} props.spacing          The spacing for the component, as a string, number, array, or object.
 * @param {number}                     props.span             Number of columns to span in CSS Grid mode.
 * @param {string}                     props.type             The grid type, one of `fixed` or `fluid`, only applies if grid is container.
 * @param {number}                     props.width            The width of the grid item in a 12-column grid, from 1 to 12.
 * @param {boolean}                    props.wrap             Whether to wrap grid items or not, only applies if grid is container.
 * @param {object|null}                ref                    Ref to the component.
 *
 * @return {JSX.Element} The functional grid component in React.
 *
 * @example
 * import Grid from '@gravityforms/components/react/admin/elements/Grid';
 *
 * return (
 *     <Grid container columnSpacing={ 4 } rowSpacing={ 4 }>
 *         <Grid item>
 *             { 'Grid item 1' }
 *         </Grid>
 *         <Grid item>
 *             { 'Grid item 2' }
 *         </Grid>
 *         <Grid item>
 *             { 'Grid item 3' }
 *         </Grid>
 *     </Grid>
 * );
 *
 */
const Grid = forwardRef( ( {
	alignItems = 'center',
	children = null,
	columns = 0,
	columnSpacing = 0,
	container,
	customAttributes = {},
	customClasses = [],
	direction = 'row',
	elementType = 'list',
	gap = 0,
	item,
	justifyContent = 'center',
	rowSpacing = 0,
	spacing = '',
	span = 0,
	type = 'fixed',
	width = 0,
	wrap = false,
}, ref ) => {
	const attributes = {
		className: classnames( {
			'gform-grid': true,
			[ `gform-grid--${ elementType }` ]: elementType,
			[ `gform-grid--${ type }` ]: container && ! columns,
			'gform-grid--wrap': container && wrap,
			'gform-grid--container': container && ! columns,
			'gform-grid--item': item,
			[ `gform-grid--col-spacing-${ columnSpacing }` ]: container && columnSpacing && Number.isInteger( columnSpacing ),
			[ `gform-grid--row-spacing-${ rowSpacing }` ]: container && rowSpacing && Number.isInteger( rowSpacing ),
			[ `gform-grid--gap-${ gap }` ]: container && gap && Number.isInteger( gap ),
			...spacerClasses( spacing ),
		}, customClasses ),
		ref,
		...customAttributes,
	};
	if ( container && columns ) {
		attributes.style = {
			display: 'grid',
			gridTemplateColumns: `repeat(${ columns }, 1fr)`,
		};
	} else if ( container ) {
		attributes.style = {
			alignItems,
			flexDirection: direction,
			justifyContent,
		};
	} else if ( span ) {
		attributes.style = {
			gridColumn: `span ${ span }`,
		};
	} else if ( width ) {
		attributes.style = {
			flex: `0 0 ${ width * 100 / 12 }%`,
		};
	}

	const isList = 'list' === elementType;
	let Component = 'div';
	if ( container && isList ) {
		Component = 'ul';
	} else if ( item && isList ) {
		Component = 'li';
	}

	return (
		<Component { ...attributes }>
			{ children }
		</Component>
	);
} );

Grid.propTypes = {
	alignItems: PropTypes.string,
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ),
	columns: PropTypes.number,
	columnSpacing: PropTypes.number,
	container: PropTypes.bool,
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	direction: PropTypes.string,
	elementType: PropTypes.oneOf( [ 'list', 'div' ] ),
	gap: PropTypes.number,
	item: PropTypes.bool,
	justifyContent: PropTypes.string,
	rowSpacing: PropTypes.number,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	span: PropTypes.number,
	type: PropTypes.oneOf( [ 'fixed', 'fluid' ] ),
	width: PropTypes.number,
	wrap: PropTypes.bool,
};

Grid.displayName = 'Grid';

export default Grid;
