import clsx from 'clsx';
import {
	useBlockProps,
	getCustomValueFromPreset,
	getSpacingPresetCssVar,
	store as blockEditorStore,
	privateApis as blockEditorPrivateApis,
	useBlockEditingMode,
} from '@wordpress/block-editor';
import { ResizableBox } from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { unlock } from '../lock-unlock';
import SpacerControls from './controls';
import { MIN_SPACER_SIZE } from './constants';

const { useSpacingSizes } = unlock( blockEditorPrivateApis );

const ResizableSpacer = ( {
	orientation,
	onResizeStart,
	onResize,
	onResizeStop,
	isSelected,
	isResizing,
	setIsResizing,
	...props
} ) => {
	const getCurrentSize = ( elt ) => {
		return orientation === 'horizontal'
			? elt.clientWidth
			: elt.clientHeight;
	};

	const getNextVal = ( elt ) => {
		return `${ getCurrentSize( elt ) }px`;
	};

	return (
		<ResizableBox
			className={ clsx( 'block-library-spacer__resize-container', {
				'resize-horizontal': orientation === 'horizontal',
				'is-resizing': isResizing,
				'is-selected': isSelected,
			} ) }
			onResizeStart={ ( _event, _direction, elt ) => {
				const nextVal = getNextVal( elt );
				onResizeStart( nextVal );
				onResize( nextVal );
			} }
			onResize={ ( _event, _direction, elt ) => {
				onResize( getNextVal( elt ) );
				if ( ! isResizing ) {
					setIsResizing( true );
				}
			} }
			onResizeStop={ ( _event, _direction, elt ) => {
				const nextVal = getCurrentSize( elt );
				onResizeStop( `${ nextVal }px` );
				setIsResizing( false );
			} }
			__experimentalShowTooltip
			__experimentalTooltipProps={ {
				axis: orientation === 'horizontal' ? 'x' : 'y',
				position: 'corner',
				isVisible: isResizing,
			} }
			showHandle={ isSelected }
			{ ...props }
		/>
	);
};

const SpacerEdit = ( {
	attributes,
	isSelected,
	setAttributes,
	toggleSelection,
	context,
	__unstableParentLayout: parentLayout,
	className,
} ) => {
	const disableCustomSpacingSizes = useSelect( ( select ) => {
		const editorSettings = select( blockEditorStore ).getSettings();
		return editorSettings?.disableCustomSpacingSizes;
	} );
	const { orientation } = context;
	const {
		orientation: parentOrientation,
		type,
		default: { type: defaultType } = {},
	} = parentLayout || {};
	// Check if the spacer is inside a flex container.
	const isFlexLayout =
		type === 'flex' || ( ! type && defaultType === 'flex' );
	// If the spacer is inside a flex container, it should either inherit the orientation
	// of the parent or use the flex default orientation.
	const inheritedOrientation =
		! parentOrientation && isFlexLayout
			? 'horizontal'
			: parentOrientation || orientation;
	const { height, width, style: blockStyle = {} } = attributes;

	const { layout = {} } = blockStyle;
	const { selfStretch, flexSize } = layout;

	const spacingSizes = useSpacingSizes();

	const [ isResizing, setIsResizing ] = useState( false );
	const [ temporaryHeight, setTemporaryHeight ] = useState( null );
	const [ temporaryWidth, setTemporaryWidth ] = useState( null );

	const onResizeStart = () => toggleSelection( false );
	const onResizeStop = () => toggleSelection( true );

	const { __unstableMarkNextChangeAsNotPersistent } =
		useDispatch( blockEditorStore );

	const handleOnVerticalResizeStop = ( newHeight ) => {
		onResizeStop();

		if ( isFlexLayout ) {
			setAttributes( {
				style: {
					...blockStyle,
					layout: {
						...layout,
						flexSize: newHeight,
						selfStretch: 'fixed',
					},
				},
			} );
		}

		setAttributes( { height: newHeight } );
		setTemporaryHeight( null );
	};

	const handleOnHorizontalResizeStop = ( newWidth ) => {
		onResizeStop();

		if ( isFlexLayout ) {
			setAttributes( {
				style: {
					...blockStyle,
					layout: {
						...layout,
						flexSize: newWidth,
						selfStretch: 'fixed',
					},
				},
			} );
		}

		setAttributes( { width: newWidth } );
		setTemporaryWidth( null );
	};

	const getHeightForVerticalBlocks = () => {
		if ( isFlexLayout ) {
			return undefined;
		}
		return temporaryHeight || getSpacingPresetCssVar( height ) || undefined;
	};

	const getWidthForHorizontalBlocks = () => {
		if ( isFlexLayout ) {
			return undefined;
		}
		return temporaryWidth || getSpacingPresetCssVar( width ) || undefined;
	};

	const sizeConditionalOnOrientation =
		inheritedOrientation === 'horizontal'
			? temporaryWidth || flexSize
			: temporaryHeight || flexSize;

	const style = {
		height:
			inheritedOrientation === 'horizontal'
				? 24
				: getHeightForVerticalBlocks(),
		width:
			inheritedOrientation === 'horizontal'
				? getWidthForHorizontalBlocks()
				: undefined,
		// In vertical flex containers, the spacer shrinks to nothing without a minimum width.
		minWidth:
			inheritedOrientation === 'vertical' && isFlexLayout
				? 48
				: undefined,
		// Add flex-basis so temporary sizes are respected.
		flexBasis: isFlexLayout ? sizeConditionalOnOrientation : undefined,
		// Remove flex-grow when resizing.
		flexGrow: isFlexLayout && isResizing ? 0 : undefined,
	};

	const resizableBoxWithOrientation = ( blockOrientation ) => {
		if ( blockOrientation === 'horizontal' ) {
			return (
				<ResizableSpacer
					minWidth={ MIN_SPACER_SIZE }
					enable={ {
						top: false,
						right: true,
						bottom: false,
						left: false,
						topRight: false,
						bottomRight: false,
						bottomLeft: false,
						topLeft: false,
					} }
					orientation={ blockOrientation }
					onResizeStart={ onResizeStart }
					onResize={ setTemporaryWidth }
					onResizeStop={ handleOnHorizontalResizeStop }
					isSelected={ isSelected }
					isResizing={ isResizing }
					setIsResizing={ setIsResizing }
				/>
			);
		}

		return (
			<>
				<ResizableSpacer
					minHeight={ MIN_SPACER_SIZE }
					enable={ {
						top: false,
						right: false,
						bottom: true,
						left: false,
						topRight: false,
						bottomRight: false,
						bottomLeft: false,
						topLeft: false,
					} }
					orientation={ blockOrientation }
					onResizeStart={ onResizeStart }
					onResize={ setTemporaryHeight }
					onResizeStop={ handleOnVerticalResizeStop }
					isSelected={ isSelected }
					isResizing={ isResizing }
					setIsResizing={ setIsResizing }
				/>
			</>
		);
	};

	useEffect( () => {
		// To avoid interfering with undo/redo operations any changes in this
		// effect must not make history and should be preceded by
		// `__unstableMarkNextChangeAsNotPersistent()`.
		const setAttributesCovertly = ( nextAttributes ) => {
			__unstableMarkNextChangeAsNotPersistent();
			setAttributes( nextAttributes );
		};

		if (
			isFlexLayout &&
			selfStretch !== 'fill' &&
			selfStretch !== 'fit' &&
			flexSize === undefined
		) {
			if ( inheritedOrientation === 'horizontal' ) {
				// If spacer is moving from a vertical container to a horizontal container,
				// it might not have width but have height instead.
				const newSize =
					getCustomValueFromPreset( width, spacingSizes ) ||
					getCustomValueFromPreset( height, spacingSizes ) ||
					'100px';
				setAttributesCovertly( {
					width: '0px',
					style: {
						...blockStyle,
						layout: {
							...layout,
							flexSize: newSize,
							selfStretch: 'fixed',
						},
					},
				} );
			} else {
				const newSize =
					getCustomValueFromPreset( height, spacingSizes ) ||
					getCustomValueFromPreset( width, spacingSizes ) ||
					'100px';
				setAttributesCovertly( {
					height: '0px',
					style: {
						...blockStyle,
						layout: {
							...layout,
							flexSize: newSize,
							selfStretch: 'fixed',
						},
					},
				} );
			}
		} else if (
			isFlexLayout &&
			( selfStretch === 'fill' || selfStretch === 'fit' )
		) {
			setAttributesCovertly(
				inheritedOrientation === 'horizontal'
					? { width: undefined }
					: { height: undefined }
			);
		} else if ( ! isFlexLayout && ( selfStretch || flexSize ) ) {
			setAttributesCovertly( {
				...( inheritedOrientation === 'horizontal'
					? { width: flexSize }
					: { height: flexSize } ),
				style: {
					...blockStyle,
					layout: {
						...layout,
						flexSize: undefined,
						selfStretch: undefined,
					},
				},
			} );
		}
	}, [
		blockStyle,
		flexSize,
		height,
		inheritedOrientation,
		isFlexLayout,
		layout,
		selfStretch,
		setAttributes,
		spacingSizes,
		width,
		__unstableMarkNextChangeAsNotPersistent,
	] );

	const blockEditingMode = useBlockEditingMode();

	return (
		<>
			<div
				{ ...useBlockProps( {
					style,
					className: clsx( className, {
						'custom-sizes-disabled': disableCustomSpacingSizes,
					} ),
				} ) }
			>
				{ blockEditingMode === 'default' &&
					resizableBoxWithOrientation( inheritedOrientation ) }
			</div>
			{ ! isFlexLayout && (
				<SpacerControls
					setAttributes={ setAttributes }
					height={ temporaryHeight || height }
					width={ temporaryWidth || width }
					orientation={ inheritedOrientation }
					isResizing={ isResizing }
				/>
			) }
		</>
	);
};

export default SpacerEdit;
