import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { isFunction } from '@gravityforms/utils';
import Button from '../../../elements/Button';
import Icon from '../../../elements/Icon';
import { getModules } from '../utils';

const { forwardRef } = React;

const ASC = 'ASC';
const DESC = 'DESC';

/**
 * @module SortableGridColumn
 * @description A sortable column for the data grid.
 *
 * @since 5.4.6
 *
 * @param {object}      props                   The component props.
 * @param {JSX.Element} props.children          The children of the component.
 * @param {object}      props.column            The column object.
 * @param {boolean}     props.isLoading         Whether the data grid is loading.
 * @param {Array}       props.modules           The modules for the data grid.
 * @param {object}      props.moduleAttributes  The module attributes.
 * @param {object}      props.moduleState       The module state.
 * @param {Function}    props.setIsLoading      The setIsLoading function.
 * @param {Function}    props.updateModuleState The updateModuleState function.
 * @param {boolean}     props.useAjax           Whether to use ajax.
 * @param {object}      ref                     The component ref.
 *
 * @return {JSX.Element} The SortableGridColumn component.
 */
const SortableGridColumn = forwardRef( ( {
	children = null,
	column = {},
	isLoading = false,
	modules = [],
	moduleAttributes = {},
	moduleState = {},
	setIsLoading = () => {},
	updateModuleState = () => {},
	useAjax = false,
}, ref ) => {
	const {
		sortButton: {
			customClasses = [],
			iconAsc = 'chevron-up',
			iconDesc = 'chevron-down',
			iconPrefix = 'gravity-component-icon',
			initialSortOrder = DESC,
			onClick = () => {},
		} = {},
	} = moduleAttributes?.columnSort || {};

	const {
		sortBy = '',
		sortOrder = '',
	} = moduleState || {};

	let icon;
	if ( column.key === sortBy && ASC === sortOrder ) {
		icon = iconAsc;
	}
	if ( column.key === sortBy && DESC === sortOrder ) {
		icon = iconDesc;
	}

	/**
	 * @function sortOnClick
	 * @description Handles the sort on click event.
	 *
	 * @since 5.4.6
	 *
	 * @param {Event} event The event object.
	 */
	const sortOnClick = ( event ) => {
		const { BulkActions, GridPagination } = getModules( modules, [ 'BulkActions', 'GridPagination' ] );

		// Set current column as sort by and set sort order to initial value.
		const updatedState = {
			sortBy: column.key,
			sortOrder: initialSortOrder,
			...( ( BulkActions && BulkActions?.getDefaultState() ) || {} ),
			...( ( GridPagination && GridPagination?.getDefaultState() ) || {} ),
		};

		// If current column is being sorted by, updated sort order.
		if ( column.key === sortBy ) {
			updatedState.sortOrder = sortOrder === ASC ? DESC : ASC;
		}

		// If ajax, do ajax and update state.
		if ( useAjax ) {
			const combinedState = { ...moduleState, ...updatedState };
			setIsLoading( true );
			onClick( combinedState )
				.then( () => {
					setIsLoading( false );
					updateModuleState( updatedState );
				} )
				.catch( ( error ) => {
					// handle error.
					setIsLoading( false );
					console.info( error );
				} );
			return;
		}

		updateModuleState( updatedState );
		onClick( event, updatedState );
	};

	const buttonClasses = classnames(
		[ 'gform-data-grid__column-sort-button', `gform-data-grid__column-sort-button--${ column.key }` ],
		customClasses,
	);

	return (
		<Button
			customClasses={ buttonClasses }
			disabled={ isLoading }
			onClick={ sortOnClick }
			type="unstyled"
			ref={ ref }
		>
			{ children }
			<Icon
				customClasses={ [ 'gform-data-grid__column-sort-button-icon' ] }
				icon={ icon }
				iconPrefix={ iconPrefix }
				spacing={ [ 0, 0, 0, 1 ] }
			/>
		</Button>
	);
} );

SortableGridColumn.propTypes = {
	column: PropTypes.object,
	isLoading: PropTypes.bool,
	moduleAttributes: PropTypes.object,
	moduleState: PropTypes.object,
	setIsLoading: PropTypes.func,
	updateModuleState: PropTypes.func,
	useAjax: PropTypes.bool,
};

/**
 * @function getSortedData
 * @description Get the sorted data.
 *
 * @since 5.4.6
 *
 * @param {object}   args              The function args.
 * @param {Array}    args.data         The data to sort.
 * @param {Function} args.sortFunction The custom sort function.
 * @param {string}   args.sortBy       The column to sort by.
 * @param {string}   args.sortOrder    The sort order.
 *
 * @return {Array} The sorted data.
 */
const getSortedData = ( {
	data = [],
	sortFunction = null,
	sortBy = '',
	sortOrder = '',
} ) => {
	if ( ! sortBy || ! sortOrder ) {
		return data;
	}

	const compareFn = isFunction( sortFunction )
		? sortFunction
		: ( a, b ) => {
			const aVal = valueResolver( a[ sortBy ] );
			const bVal = valueResolver( b[ sortBy ] );
			if ( aVal < bVal ) {
				return sortOrder === ASC ? -1 : 1;
			}
			if ( aVal > bVal ) {
				return sortOrder === ASC ? 1 : -1;
			}
			return 0;
		};

	return data.toSorted( compareFn );
};

/**
 * @function valueResolver
 * @description Resolve the value of a cell. The cell can be a string, an object with a value property, or an object with a props object containing a content property.
 *
 * @since 5.4.6
 *
 * @param {object|string} cell The cell to resolve.
 *
 * @return {string|*} The resolved value.
 */
const valueResolver = ( cell ) => {
	const cellType = typeof cell;
	if ( cellType === 'string' || cellType === 'number' ) {
		return cell;
	}
	if ( cell?.value !== undefined ) {
		return cell.value;
	}
	if ( cell?.props?.content !== undefined ) {
		return cell.props.content;
	}
	return '';
};

/**
 * @function getDefaultState
 * @description Get the default state for the ColumnSort module.
 *
 * @since 5.4.6
 *
 * @return {object} The default state.
 */
const getDefaultState = () => ( {
	sortBy: '',
	sortOrder: '',
} );

export default {
	name: 'ColumnSort',
	SortableGridColumn,
	getDefaultState,
	getSortedData,
	valueResolver,
	ASC,
	DESC,
};
