import { React, ReactDND, ReactDNDHtml5Backend, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses, sprintf } from '@gravityforms/utils';
import itemTypes from './item-types';
import KanbanLoader from './KanbanLoader';
import Box from '../../elements/Box';
import Button from '../../elements/Button';
import Grid from '../../elements/Grid';
import Input from '../../elements/Input';
import StatusIndicator from '../../elements/StatusIndicator';
import Text from '../../elements/Text';
import Droplist from '../Droplist';
import Overlay from '../Overlay';
import Swatch from '../Swatch';
import { GRAY, STATUSES, SWATCH_PALETTE, STATUS_SWATCH_MAP } from './swatch';

const { useCallback, useEffect, useRef, useState } = React;
const { useDrag, useDrop } = ReactDND;
const { getEmptyImage } = ReactDNDHtml5Backend;

const NEEDS_I18N_LABEL = 'Needs i18n';

/**
 * @module KanbanColumn
 * @description A column in the Kanban board.
 *
 * @since 5.8.4
 *
 * @param {object}                     props                       The props for the Kanban column.
 * @param {boolean}                    props.cardHoveredWhenEmpty  Whether the card is hovered when empty.
 * @param {JSX.Element}                props.children              The children elements to render inside the column.
 * @param {object}                     props.columnCountAttributes Attributes for the column count.
 * @param {string|Array|object}        props.columnCountClasses    Classes for the column count.
 * @param {object}                     props.columnData            Data for the column.
 * @param {object}                     props.columnLabelAttributes Attributes for the column label.
 * @param {string|Array|object}        props.columnLabelClasses    Classes for the column label.
 * @param {object}                     props.customAttributes      Custom attributes for the column.
 * @param {string|Array|object}        props.customClasses         Custom classes for the column.
 * @param {boolean}                    props.deleteDisabled        Whether deleting the column is disabled.
 * @param {object}                     props.dragHandleAttributes  Attributes for the drag handle.
 * @param {string|Array|object}        props.dragHandleClasses     Classes for the drag handle.
 * @param {boolean}                    props.hoveredLeft           Whether the column is hovered on the left.
 * @param {boolean}                    props.hoveredRight          Whether the column is hovered on the right.
 * @param {object}                     props.i18n                  I18n strings for the column.
 * @param {string}                     props.id                    The ID of the column.
 * @param {number}                     props.index                 The index of the column.
 * @param {boolean}                    props.isDragLayer           Whether this is being rendered in a drag layer.
 * @param {boolean}                    props.isFirstColumn         Whether this is the first column.
 * @param {boolean}                    props.isLastColumn          Whether this is the last column.
 * @param {string}                     props.kanbanId              The ID of the Kanban board.
 * @param {Function}                   props.onDelete              Callback when the column is deleted.
 * @param {Function}                   props.moveColumn            Function to move the column.
 * @param {Function}                   props.onDragEnd             Callback when dragging column ends.
 * @param {boolean}                    props.isLoading             Whether the column is in a loading state.
 * @param {Function}                   props.onDrop                Callback when a card is dropped on the column.
 * @param {Function}                   props.onHover               Callback when a card is hovering over the column.
 * @param {string|number|Array|object} props.spacing               Spacing for the column.
 * @param {Function}                   props.updateColumnLabel     Function to update the column label.
 *
 * @return {JSX.Element} The Kanban column component.
 */
const KanbanColumn = ( {
	cardHoveredWhenEmpty = false,
	children = null,
	columnCountAttributes = {},
	columnCountClasses = [],
	columnData = {},
	columnLabelAttributes = {},
	columnLabelClasses = [],
	customAttributes = {},
	customClasses = [],
	deleteDisabled = false,
	dragHandleAttributes = {},
	dragHandleClasses = [],
	hoveredLeft = false,
	hoveredRight = false,
	i18n = {},
	id = '',
	index = 0,
	isDragLayer = false,
	isFirstColumn = false,
	isLastColumn = false,
	isLoading = false,
	kanbanId = '',
	moveColumn = () => {},
	onDelete = () => {},
	onDragEnd = () => {},
	onDrop = () => {},
	onHover = () => {},
	spacing = '',
	updateColumnLabel = () => {},
} ) => {
	const columnItemType = `${ itemTypes.KANBAN_COLUMN }_${ kanbanId }`;
	const cardItemType = `${ itemTypes.KANBAN_CARD }_${ kanbanId }`;

	const [ overlayOpen, setOverlayOpen ] = useState( false );
	const [ columnLabel, setColumnLabel ] = useState( columnLabelAttributes.label || '' );
	const [ labelPillStyle, setLabelPillStyle ] = useState( STATUSES.includes( columnLabelAttributes.status?.toLowerCase() ) ? columnLabelAttributes.status?.toLowerCase() : GRAY );
	const columnRef = useRef( null );

	const [ { isDragging }, drag, preview ] = useDrag( {
		type: columnItemType,
		item: () => {
			const item = {
				id,
				index,
				column: columnData,
			};
			if ( columnRef.current ) {
				item.rect = columnRef.current.getBoundingClientRect();
			}
			return item;
		},
		collect: ( monitor ) => ( {
			isDragging: monitor.isDragging(),
		} ),
		end: () => {
			onDragEnd();
		},
	} );

	const [ , drop ] = useDrop( {
		accept: cardItemType,
		hover: ( item, monitor ) => {
			onHover( item, monitor, id );
		},
		drop: ( item ) => {
			onDrop( item );
		},
	} );

	useEffect( () => {
		preview( getEmptyImage(), { captureDraggingState: true } );
	}, [] ); // eslint-disable-line react-hooks/exhaustive-deps

	const getColumnGridItemProps = ( type = '' ) => ( {
		customClasses: [ 'gform-kanban__column-grid-item', `gform-kanban__column-grid-item--${ type }` ],
		elementType: 'div',
		item: true,
		spacing: type !== 'cards' ? 3 : 0,
	} );

	const componentProps = {
		...customAttributes,
		className: classnames( {
			'gform-kanban__column': true,
			'gform-kanban__column--is-dragging': isDragging && ! isDragLayer,
			'gform-kanban__column--drag-layer': isDragLayer,
			'gform-kanban__column--drop-left': hoveredLeft,
			'gform-kanban__column--drop-right': hoveredRight,
			'gform-kanban__column--card-drop-when-empty': cardHoveredWhenEmpty,
			'gform-kanban__column--loading': isLoading,
			...spacerClasses( spacing ),
		}, customClasses ),
		'data-index': index,
		id,
	};

	const columnGridProps = {
		alignItems: 'stretch',
		container: true,
		customClasses: [ 'gform-kanban__column-grid' ],
		direction: 'column',
		elementType: 'div',
		justifyContent: 'flex-start',
	};

	const headerProps = {
		customClasses: [ 'gform-kanban__column-header' ],
		display: isLoading ? 'block' : 'flex',
	};

	const headerLabelProps = {
		customClasses: [ 'gform-kanban__column-header-label' ],
		display: isLoading ? 'block' : 'flex',
	};

	const headerActionsProps = {
		customClasses: [ 'gform-kanban__column-header-actions' ],
		display: 'flex',
	};

	const columnLabelProps = {
		hasDot: false,
		isStatic: false,
		status: columnLabelAttributes.status || GRAY,
		...columnLabelAttributes,
		customAttributes: {
			...( columnLabelAttributes?.customAttributes || {} ),
			onClick: () => setOverlayOpen( true ),
		},
		customClasses: classnames( {
			'gform-kanban__column-label': true,
		}, columnLabelClasses ),
	};

	const columnCountProps = {
		hasDot: false,
		status: GRAY,
		spacing: [ 0, 0, 0, 1 ],
		...columnCountAttributes,
		customClasses: classnames( {
			'gform-kanban__column-count': true,
		}, columnCountClasses ),
	};

	const droplistProps = {
		align: 'right',
		closeOnClick: true,
		id: `${ id }-droplist`,
		listItems: [
			{
				key: `${ id }-move-left`,
				props: {
					element: 'button',
					id: `${ id }-move-left`,
					label: i18n?.moveLeft || NEEDS_I18N_LABEL,
					iconBefore: 'arrow-narrow-left',
					customAttributes: {
						disabled: isFirstColumn,
						onClick: () => {
							if ( isFirstColumn ) {
								return;
							}
							moveColumn( index, index - 1, id );
						},
					},
				},
			},
			{
				key: `${ id }-move-right`,
				props: {
					element: 'button',
					id: `${ id }-move-right`,
					label: i18n?.moveRight || NEEDS_I18N_LABEL,
					iconBefore: 'arrow-narrow-right',
					customAttributes: {
						disabled: isLastColumn,
						onClick: () => {
							if ( isLastColumn ) {
								return;
							}
							moveColumn( index, index + 1, id );
						},
					},
				},
			},
			{
				key: `${ id }-delete-column`,
				props: {
					element: 'button',
					id: `${ id }-delete-column`,
					label: i18n?.deleteColumn || NEEDS_I18N_LABEL,
					iconBefore: 'trash',
					style: 'error',
					customAttributes: {
						disabled: deleteDisabled,
						onClick: () => {
							if ( deleteDisabled ) {
								return;
							}
							onDelete( id );
						},
					},
				},
			},
		],
		triggerAttributes: {
			icon: 'dots-horizontal',
			iconPrefix: 'gravity-component-icon',
			size: 'size-height-s',
			title: i18n?.columnActionsTrigger ? sprintf( i18n.columnActionsTrigger, columnLabelAttributes?.label || '' ) : NEEDS_I18N_LABEL,
			type: 'icon-white',
		},
	};

	const dragHandleProps = {
		size: 'size-height-m',
		type: 'icon-grey',
		icon: 'drag-indicator',
		iconPrefix: 'gravity-component-icon',
		spacing: [ 0, 0, 0, 1 ],
		...dragHandleAttributes,
		customClasses: classnames( {
			'gform-kanban__column-drag-handle': true,
		}, dragHandleClasses ),
	};

	const overlayProps = {
		confirmButtonAttributes: {
			disabled: ! columnLabel.trim(),
		},
		customClasses: [ 'gform-kanban__column-overlay' ],
		hasConfirm: true,
		i18n: {
			cancel: i18n?.overlayCancel || NEEDS_I18N_LABEL,
			confirm: i18n?.overlayConfirm || NEEDS_I18N_LABEL,
		},
		isOpen: overlayOpen,
		onConfirm: () => {
			setOverlayOpen( false );
			updateColumnLabel( id, { label: columnLabel, status: labelPillStyle } );
		},
		onCancel: () => {
			setOverlayOpen( false );
			setTimeout( () => {
				setColumnLabel( columnLabelAttributes.label || '' );
				setLabelPillStyle( STATUSES.includes( columnLabelAttributes.status?.toLowerCase() ) ? columnLabelAttributes.status?.toLowerCase() : GRAY );
			}, 250 );
		},
		onClose: () => {
			setOverlayOpen( false );
		},
	};

	const overlayInputProps = {
		controlled: true,
		id: `${ id }-overlay-input`,
		name: `${ id }-overlay-input`,
		labelAttributes: {
			label: i18n?.overlayInputLabel || NEEDS_I18N_LABEL,
			size: 'text-sm',
			weight: 'medium',
		},
		onChange: ( value ) => {
			setColumnLabel( value );
		},
		spacing: 3,
		value: columnLabel,
		width: 'full',
	};

	const overlaySwatchLabelProps = {
		customAttributes: {
			id: `${ id }-overlay-swatch-label`,
		},
		content: i18n?.overlaySwatchLabel || NEEDS_I18N_LABEL,
		size: 'text-sm',
		weight: 'medium',
	};

	const overlaySwatchProps = {
		allowNew: false,
		controlled: true,
		customAttributes: {
			'aria-labelledby': `${ id }-overlay-swatch-label`,
		},
		customClasses: [ 'gform-kanban__column-overlay-swatch' ],
		id: `${ id }-overlay-swatch`,
		name: `${ id }-overlay-swatch`,
		onChange: ( value ) => {
			const status = Object.keys( STATUS_SWATCH_MAP ).find( ( key ) => STATUS_SWATCH_MAP[ key ] === value );
			if ( status ) {
				setLabelPillStyle( status );
			}
		},
		palette: SWATCH_PALETTE,
		value: STATUS_SWATCH_MAP[ labelPillStyle ],
	};

	const setRefs = useCallback( ( node ) => {
		columnRef.current = node;
		drop( node );
	}, [ drop ] );

	return (
		<div { ...componentProps } ref={ setRefs }>
			<Grid { ...columnGridProps }>
				<Grid { ...getColumnGridItemProps( 'header' ) }>
					<Box { ...headerProps }>
						<Box { ...headerLabelProps }>
							{
								isLoading
									? <KanbanLoader />
									: <>
										<StatusIndicator { ...columnLabelProps } />
										<StatusIndicator { ...columnCountProps } />
									</>
							}
						</Box>
						{ ! isLoading && (
							<>
								<Box { ...headerActionsProps }>
									<Droplist { ...droplistProps } />
									<Button { ...dragHandleProps } ref={ drag } />
								</Box>
								{ ! columnLabelProps.isStatic && overlayOpen && (
									<Overlay { ...overlayProps }>
										<Input { ...overlayInputProps } />
										<Text { ...overlaySwatchLabelProps } />
										<Swatch { ...overlaySwatchProps } />
									</Overlay>
								) }
							</>
						) }
					</Box>
				</Grid>
				<Grid { ...getColumnGridItemProps( 'cards' ) }>
					{ children }
				</Grid>
			</Grid>
		</div>
	);
};

KanbanColumn.propTypes = {
	cardHoveredWhenEmpty: PropTypes.bool,
	children: PropTypes.oneOfType( [
		PropTypes.arrayOf( PropTypes.node ),
		PropTypes.node,
	] ),
	columnCountAttributes: PropTypes.object,
	columnCountClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	columnLabelAttributes: PropTypes.object,
	columnLabelClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	deleteDisabled: PropTypes.bool,
	dragHandleAttributes: PropTypes.object,
	dragHandleClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	hoveredLeft: PropTypes.bool,
	hoveredRight: PropTypes.bool,
	i18n: PropTypes.object,
	id: PropTypes.string,
	index: PropTypes.number,
	isFirstColumn: PropTypes.bool,
	isLastColumn: PropTypes.bool,
	isLoading: PropTypes.bool,
	kanbanId: PropTypes.string,
	moveColumn: PropTypes.func,
	onDelete: PropTypes.func,
	onDragEnd: PropTypes.func,
	onDrop: PropTypes.func,
	onHover: PropTypes.func,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	updateColumnLabel: PropTypes.func,
};

KanbanColumn.displayName = 'KanbanColumn';

export default KanbanColumn;
