import clsx from 'clsx';
import {
	memo,
	useCallback,
	useEffect,
	useRef,
	useState,
} from '@wordpress/element';
import {
	InspectorControls,
	BlockControls,
	RichText,
	BlockIcon,
	AlignmentControl,
	useBlockProps,
	__experimentalUseColorProps as useColorProps,
	__experimentalUseBorderProps as useBorderProps,
	useBlockEditingMode,
} from '@wordpress/block-editor';
import { __, _x } from '@wordpress/i18n';
import {
	Button,
	Placeholder,
	TextControl,
	ToggleControl,
	ToolbarDropdownMenu,
	__experimentalHasSplitBorders as hasSplitBorders,
	__experimentalToolsPanel as ToolsPanel,
	__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
import {
	alignLeft,
	alignRight,
	alignCenter,
	blockTable as icon,
	tableColumnAfter,
	tableColumnBefore,
	tableColumnDelete,
	tableRowAfter,
	tableRowBefore,
	tableRowDelete,
	table,
} from '@wordpress/icons';
import {
	createTable,
	updateSelectedCell,
	getCellAttribute,
	insertRow,
	deleteRow,
	insertColumn,
	deleteColumn,
	toggleSection,
	isEmptyTableSection,
} from './state';
import { Caption } from '../utils/caption';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';

const ALIGNMENT_CONTROLS = [
	{
		icon: alignLeft,
		title: __( 'Align column left' ),
		align: 'left',
	},
	{
		icon: alignCenter,
		title: __( 'Align column center' ),
		align: 'center',
	},
	{
		icon: alignRight,
		title: __( 'Align column right' ),
		align: 'right',
	},
];

const cellAriaLabel = {
	head: __( 'Header cell text' ),
	body: __( 'Body cell text' ),
	foot: __( 'Footer cell text' ),
};

const placeholder = {
	head: _x( 'Header label', 'table header' ),
	foot: _x( 'Footer label', 'table footer' ),
};

function TSection( { name, ...props } ) {
	const TagName = `t${ name }`;
	return <TagName { ...props } />;
}

function TableEdit( {
	attributes,
	setAttributes,
	insertBlocksAfter,
	isSelected: isSingleSelected,
} ) {
	const { hasFixedLayout, head, foot } = attributes;
	const [ initialRowCount, setInitialRowCount ] = useState( 2 );
	const [ initialColumnCount, setInitialColumnCount ] = useState( 2 );
	const [ selectedCell, setSelectedCell ] = useState();

	const colorProps = useColorProps( attributes );
	const borderProps = useBorderProps( attributes );
	const blockEditingMode = useBlockEditingMode();

	const tableRef = useRef();
	const [ hasTableCreated, setHasTableCreated ] = useState( false );

	const dropdownMenuProps = useToolsPanelDropdownMenuProps();

	/**
	 * Updates the initial column count used for table creation.
	 *
	 * @param {number} count New initial column count.
	 */
	function onChangeInitialColumnCount( count ) {
		setInitialColumnCount( count );
	}

	/**
	 * Updates the initial row count used for table creation.
	 *
	 * @param {number} count New initial row count.
	 */
	function onChangeInitialRowCount( count ) {
		setInitialRowCount( count );
	}

	/**
	 * Creates a table based on dimensions in local state.
	 *
	 * @param {Object} event Form submit event.
	 */
	function onCreateTable( event ) {
		event.preventDefault();

		setAttributes(
			createTable( {
				rowCount: parseInt( initialRowCount, 10 ) || 2,
				columnCount: parseInt( initialColumnCount, 10 ) || 2,
			} )
		);
		setHasTableCreated( true );
	}

	/**
	 * Toggles whether the table has a fixed layout or not.
	 */
	function onChangeFixedLayout() {
		setAttributes( { hasFixedLayout: ! hasFixedLayout } );
	}

	/**
	 * Changes the content of the currently selected cell.
	 *
	 * @param {Array} content A RichText content value.
	 */
	const onChange = useCallback(
		function ( content ) {
			if ( ! selectedCell ) {
				return;
			}

			setAttributes( ( currentAttributes ) =>
				updateSelectedCell(
					currentAttributes,
					selectedCell,
					( cellAttributes ) => ( {
						...cellAttributes,
						content,
					} )
				)
			);
		},
		[ selectedCell, setAttributes ]
	);

	/**
	 * Align text within the a column.
	 *
	 * @param {string} align The new alignment to apply to the column.
	 */
	function onChangeColumnAlignment( align ) {
		if ( ! selectedCell ) {
			return;
		}

		// Convert the cell selection to a column selection so that alignment
		// is applied to the entire column.
		const columnSelection = {
			type: 'column',
			columnIndex: selectedCell.columnIndex,
		};

		const newAttributes = updateSelectedCell(
			attributes,
			columnSelection,
			( cellAttributes ) => ( {
				...cellAttributes,
				align,
			} )
		);
		setAttributes( newAttributes );
	}

	/**
	 * Get the alignment of the currently selected cell.
	 *
	 * @return {string | undefined} The new alignment to apply to the column.
	 */
	function getCellAlignment() {
		if ( ! selectedCell ) {
			return;
		}

		return getCellAttribute( attributes, selectedCell, 'align' );
	}

	/**
	 * Add or remove a `head` table section.
	 */
	function onToggleHeaderSection() {
		setAttributes( toggleSection( attributes, 'head' ) );
	}

	/**
	 * Add or remove a `foot` table section.
	 */
	function onToggleFooterSection() {
		setAttributes( toggleSection( attributes, 'foot' ) );
	}

	/**
	 * Inserts a row at the currently selected row index, plus `delta`.
	 *
	 * @param {number} delta Offset for selected row index at which to insert.
	 */
	function onInsertRow( delta ) {
		if ( ! selectedCell ) {
			return;
		}

		const { sectionName, rowIndex } = selectedCell;
		const newRowIndex = rowIndex + delta;

		setAttributes(
			insertRow( attributes, {
				sectionName,
				rowIndex: newRowIndex,
			} )
		);
		// Select the first cell of the new row.
		setSelectedCell( {
			sectionName,
			rowIndex: newRowIndex,
			columnIndex: 0,
			type: 'cell',
		} );
	}

	/**
	 * Inserts a row before the currently selected row.
	 */
	function onInsertRowBefore() {
		onInsertRow( 0 );
	}

	/**
	 * Inserts a row after the currently selected row.
	 */
	function onInsertRowAfter() {
		onInsertRow( 1 );
	}

	/**
	 * Deletes the currently selected row.
	 */
	function onDeleteRow() {
		if ( ! selectedCell ) {
			return;
		}

		const { sectionName, rowIndex } = selectedCell;

		setSelectedCell();
		setAttributes( deleteRow( attributes, { sectionName, rowIndex } ) );
	}

	/**
	 * Inserts a column at the currently selected column index, plus `delta`.
	 *
	 * @param {number} delta Offset for selected column index at which to insert.
	 */
	function onInsertColumn( delta = 0 ) {
		if ( ! selectedCell ) {
			return;
		}

		const { columnIndex } = selectedCell;
		const newColumnIndex = columnIndex + delta;

		setAttributes(
			insertColumn( attributes, {
				columnIndex: newColumnIndex,
			} )
		);
		// Select the first cell of the new column.
		setSelectedCell( {
			rowIndex: 0,
			columnIndex: newColumnIndex,
			type: 'cell',
		} );
	}

	/**
	 * Inserts a column before the currently selected column.
	 */
	function onInsertColumnBefore() {
		onInsertColumn( 0 );
	}

	/**
	 * Inserts a column after the currently selected column.
	 */
	function onInsertColumnAfter() {
		onInsertColumn( 1 );
	}

	/**
	 * Deletes the currently selected column.
	 */
	function onDeleteColumn() {
		if ( ! selectedCell ) {
			return;
		}

		const { sectionName, columnIndex } = selectedCell;

		setSelectedCell();
		setAttributes(
			deleteColumn( attributes, { sectionName, columnIndex } )
		);
	}

	useEffect( () => {
		if ( ! isSingleSelected ) {
			setSelectedCell();
		}
	}, [ isSingleSelected ] );

	useEffect( () => {
		if ( hasTableCreated ) {
			tableRef?.current
				?.querySelector( 'td div[contentEditable="true"]' )
				?.focus();
			setHasTableCreated( false );
		}
	}, [ hasTableCreated ] );

	const sections = [ 'head', 'body', 'foot' ].filter(
		( name ) => ! isEmptyTableSection( attributes[ name ] )
	);

	const tableControls = [
		{
			icon: tableRowBefore,
			title: __( 'Insert row before' ),
			isDisabled: ! selectedCell,
			onClick: onInsertRowBefore,
		},
		{
			icon: tableRowAfter,
			title: __( 'Insert row after' ),
			isDisabled: ! selectedCell,
			onClick: onInsertRowAfter,
		},
		{
			icon: tableRowDelete,
			title: __( 'Delete row' ),
			isDisabled: ! selectedCell,
			onClick: onDeleteRow,
		},
		{
			icon: tableColumnBefore,
			title: __( 'Insert column before' ),
			isDisabled: ! selectedCell,
			onClick: onInsertColumnBefore,
		},
		{
			icon: tableColumnAfter,
			title: __( 'Insert column after' ),
			isDisabled: ! selectedCell,
			onClick: onInsertColumnAfter,
		},
		{
			icon: tableColumnDelete,
			title: __( 'Delete column' ),
			isDisabled: ! selectedCell,
			onClick: onDeleteColumn,
		},
	];

	const renderedSections = sections.map( ( name ) => (
		<TSection name={ name } key={ name }>
			{ attributes[ name ].map( ( { cells }, rowIndex ) => (
				<tr key={ rowIndex }>
					{ cells.map( ( cellProps, columnIndex ) => {
						const isSelected =
							selectedCell?.sectionName === name &&
							selectedCell?.rowIndex === rowIndex &&
							selectedCell?.columnIndex === columnIndex;

						// Important - the Cell component is memoized to improve typing performance.
						// ensure all props passed have stable references.
						return (
							<Cell
								key={ columnIndex }
								name={ name }
								rowIndex={ rowIndex }
								columnIndex={ columnIndex }
								onChange={
									// Only pass the `onChange` handler to the selectedCell.
									// Cell components are memoized, so it's best to avoid
									// passing in a value that will cause all cells to re-render
									// whenever it changes.
									isSelected ? onChange : undefined
								}
								setSelectedCell={ setSelectedCell }
								{ ...cellProps }
							/>
						);
					} ) }
				</tr>
			) ) }
		</TSection>
	) );

	const isEmpty = ! sections.length;

	return (
		<figure { ...useBlockProps( { ref: tableRef } ) }>
			{ ! isEmpty && blockEditingMode === 'default' && (
				<>
					<BlockControls group="block">
						<AlignmentControl
							label={ __( 'Align column content' ) }
							alignmentControls={ ALIGNMENT_CONTROLS }
							value={ getCellAlignment() }
							onChange={ ( nextAlign ) =>
								onChangeColumnAlignment( nextAlign )
							}
						/>
					</BlockControls>
					<BlockControls group="other">
						<ToolbarDropdownMenu
							icon={ table }
							label={ __( 'Edit table' ) }
							controls={ tableControls }
						/>
					</BlockControls>
				</>
			) }
			<InspectorControls>
				<ToolsPanel
					label={ __( 'Settings' ) }
					resetAll={ () => {
						setAttributes( {
							hasFixedLayout: true,
							head: [],
							foot: [],
						} );
					} }
					dropdownMenuProps={ dropdownMenuProps }
				>
					<ToolsPanelItem
						hasValue={ () => hasFixedLayout !== true }
						label={ __( 'Fixed width table cells' ) }
						onDeselect={ () =>
							setAttributes( { hasFixedLayout: true } )
						}
						isShownByDefault
					>
						<ToggleControl
							label={ __( 'Fixed width table cells' ) }
							checked={ !! hasFixedLayout }
							onChange={ onChangeFixedLayout }
						/>
					</ToolsPanelItem>
					{ ! isEmpty && (
						<>
							<ToolsPanelItem
								hasValue={ () => head && head.length }
								label={ __( 'Header section' ) }
								onDeselect={ () =>
									setAttributes( { head: [] } )
								}
								isShownByDefault
							>
								<ToggleControl
									label={ __( 'Header section' ) }
									checked={ !! ( head && head.length ) }
									onChange={ onToggleHeaderSection }
								/>
							</ToolsPanelItem>
							<ToolsPanelItem
								hasValue={ () => foot && foot.length }
								label={ __( 'Footer section' ) }
								onDeselect={ () =>
									setAttributes( { foot: [] } )
								}
								isShownByDefault
							>
								<ToggleControl
									label={ __( 'Footer section' ) }
									checked={ !! ( foot && foot.length ) }
									onChange={ onToggleFooterSection }
								/>
							</ToolsPanelItem>
						</>
					) }
				</ToolsPanel>
			</InspectorControls>
			{ ! isEmpty && (
				<table
					className={ clsx(
						colorProps.className,
						borderProps.className,
						{
							'has-fixed-layout': hasFixedLayout,
							// This is required in the editor only to overcome
							// the fact the editor rewrites individual border
							// widths into a shorthand format.
							'has-individual-borders': hasSplitBorders(
								attributes?.style?.border
							),
						}
					) }
					style={ { ...colorProps.style, ...borderProps.style } }
				>
					{ renderedSections }
				</table>
			) }
			{ isEmpty ? (
				<Placeholder
					label={ __( 'Table' ) }
					icon={ <BlockIcon icon={ icon } showColors /> }
					instructions={ __( 'Insert a table for sharing data.' ) }
				>
					<form
						className="blocks-table__placeholder-form"
						onSubmit={ onCreateTable }
					>
						<TextControl
							type="number"
							label={ __( 'Column count' ) }
							value={ initialColumnCount }
							onChange={ onChangeInitialColumnCount }
							min="1"
							className="blocks-table__placeholder-input"
						/>
						<TextControl
							type="number"
							label={ __( 'Row count' ) }
							value={ initialRowCount }
							onChange={ onChangeInitialRowCount }
							min="1"
							className="blocks-table__placeholder-input"
						/>
						<Button
							__next40pxDefaultSize
							variant="primary"
							type="submit"
						>
							{ __( 'Create Table' ) }
						</Button>
					</form>
				</Placeholder>
			) : (
				<Caption
					attributes={ attributes }
					setAttributes={ setAttributes }
					isSelected={ isSingleSelected }
					insertBlocksAfter={ insertBlocksAfter }
					label={ __( 'Table caption text' ) }
					showToolbarButton={
						isSingleSelected && blockEditingMode === 'default'
					}
				/>
			) }
		</figure>
	);
}

const Cell = memo( function ( {
	tag: CellTag,
	name,
	scope,
	colspan,
	rowspan,
	rowIndex,
	columnIndex,
	align,
	content,
	onChange,
	setSelectedCell,
} ) {
	return (
		<CellTag
			scope={ CellTag === 'th' ? scope : undefined }
			colSpan={ colspan }
			rowSpan={ rowspan }
			className={ clsx(
				{
					[ `has-text-align-${ align }` ]: align,
				},
				'wp-block-table__cell-content'
			) }
		>
			<RichText
				identifier={ `${ name }.${ rowIndex }.cells.${ columnIndex }.content` }
				value={ content }
				onChange={ onChange }
				onFocus={ () => {
					setSelectedCell( {
						sectionName: name,
						rowIndex,
						columnIndex,
						type: 'cell',
					} );
				} }
				aria-label={ cellAriaLabel[ name ] }
				placeholder={ placeholder[ name ] }
			/>
		</CellTag>
	);
} );

export default TableEdit;
