/**
 * Structural editing actions for the table, driven from a selected cell.
 *
 * The inner blocks (sections → rows → cells) are the single source of truth.
 * These actions add/remove rows and columns and merge/split cells by operating
 * directly on the block tree — no numeric row/column attribute syncing, so an
 * edit can never "reset" the table.
 */

import { useRegistry } from '@wordpress/data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { useCallback } from '@wordpress/element';
import { createTableCellBlock, createTableRowBlock } from './utils';

export function useTableCellActions( cellClientId ) {
	const registry = useRegistry();

	// Resolve where this cell sits in the table (fresh at call time).
	const resolve = useCallback( () => {
		const sel = registry.select( blockEditorStore );
		const rowId = sel.getBlockRootClientId( cellClientId );
		const sectionId = rowId ? sel.getBlockRootClientId( rowId ) : null;
		const tableId = sectionId
			? sel.getBlockRootClientId( sectionId )
			: null;
		if ( ! rowId || ! sectionId || ! tableId ) {
			return null;
		}
		const rowOrder = sel.getBlockOrder( sectionId );
		const cellOrder = sel.getBlockOrder( rowId );
		return {
			sel,
			rowId,
			sectionId,
			tableId,
			sectionType:
				sel.getBlockAttributes( sectionId )?.sectionType || 'body',
			rowIndex: rowOrder.indexOf( rowId ),
			colIndex: cellOrder.indexOf( cellClientId ),
			rowOrder,
			cellOrder,
			sectionOrder: sel.getBlockOrder( tableId ),
		};
	}, [ registry, cellClientId ] );

	// New cells are always created as data cells; the tag (td/th) is changed
	// manually per cell from the cell settings.

	const insertRow = useCallback(
		( offset ) => {
			const ctx = resolve();
			if ( ! ctx ) {
				return;
			}
			const columns = Math.max( 1, ctx.cellOrder.length );
			const newRow = createTableRowBlock( { columns } );
			registry
				.dispatch( blockEditorStore )
				.insertBlocks(
					newRow,
					ctx.rowIndex + offset,
					ctx.sectionId,
					false
				);
		},
		[ registry, resolve ]
	);

	const deleteRow = useCallback( () => {
		const ctx = resolve();
		if ( ! ctx || ctx.rowOrder.length <= 1 ) {
			return;
		}
		registry.dispatch( blockEditorStore ).removeBlock( ctx.rowId, false );
	}, [ registry, resolve ] );

	const insertColumn = useCallback(
		( offset ) => {
			const ctx = resolve();
			if ( ! ctx ) {
				return;
			}
			const { sel, colIndex, sectionOrder } = ctx;
			const dispatch = registry.dispatch( blockEditorStore );
			sectionOrder.forEach( ( sectionId ) => {
				sel.getBlockOrder( sectionId ).forEach( ( rowId ) => {
					const at = Math.min(
						colIndex + offset,
						sel.getBlockOrder( rowId ).length
					);
					dispatch.insertBlocks(
						createTableCellBlock( { tag: 'td' } ),
						at,
						rowId,
						false
					);
				} );
			} );
		},
		[ registry, resolve ]
	);

	const deleteColumn = useCallback( () => {
		const ctx = resolve();
		if ( ! ctx || ctx.cellOrder.length <= 1 ) {
			return;
		}
		const { sel, colIndex, sectionOrder } = ctx;
		const toRemove = [];
		sectionOrder.forEach( ( sectionId ) => {
			sel.getBlockOrder( sectionId ).forEach( ( rowId ) => {
				const cell = sel.getBlockOrder( rowId )[ colIndex ];
				if ( cell ) {
					toRemove.push( cell );
				}
			} );
		} );
		if ( toRemove.length ) {
			registry
				.dispatch( blockEditorStore )
				.removeBlocks( toRemove, false );
		}
	}, [ registry, resolve ] );

	const mergeRight = useCallback( () => {
		const ctx = resolve();
		if ( ! ctx ) {
			return;
		}
		const right = ctx.cellOrder[ ctx.colIndex + 1 ];
		if ( ! right ) {
			return;
		}
		const cur = ctx.sel.getBlockAttributes( cellClientId );
		const nbr = ctx.sel.getBlockAttributes( right );
		const dispatch = registry.dispatch( blockEditorStore );
		dispatch.updateBlockAttributes( cellClientId, {
			colSpan: ( cur?.colSpan || 1 ) + ( nbr?.colSpan || 1 ),
		} );
		dispatch.removeBlock( right, false );
	}, [ registry, resolve, cellClientId ] );

	const mergeDown = useCallback( () => {
		const ctx = resolve();
		if ( ! ctx ) {
			return;
		}
		const belowRow = ctx.rowOrder[ ctx.rowIndex + 1 ];
		if ( ! belowRow ) {
			return;
		}
		const below = ctx.sel.getBlockOrder( belowRow )[ ctx.colIndex ];
		if ( ! below ) {
			return;
		}
		const cur = ctx.sel.getBlockAttributes( cellClientId );
		const nbr = ctx.sel.getBlockAttributes( below );
		const dispatch = registry.dispatch( blockEditorStore );
		dispatch.updateBlockAttributes( cellClientId, {
			rowSpan: ( cur?.rowSpan || 1 ) + ( nbr?.rowSpan || 1 ),
		} );
		dispatch.removeBlock( below, false );
	}, [ registry, resolve, cellClientId ] );

	const splitCell = useCallback( () => {
		const ctx = resolve();
		if ( ! ctx ) {
			return;
		}
		const cur = ctx.sel.getBlockAttributes( cellClientId ) || {};
		const colSpan = cur.colSpan || 1;
		const rowSpan = cur.rowSpan || 1;
		if ( colSpan <= 1 && rowSpan <= 1 ) {
			return;
		}
		const dispatch = registry.dispatch( blockEditorStore );
		dispatch.updateBlockAttributes( cellClientId, {
			colSpan: 1,
			rowSpan: 1,
		} );

		// Restore the columns this cell was covering in its own row.
		const extra = [];
		for ( let i = 1; i < colSpan; i++ ) {
			extra.push( createTableCellBlock( { tag: 'td' } ) );
		}
		if ( extra.length ) {
			dispatch.insertBlocks( extra, ctx.colIndex + 1, ctx.rowId, false );
		}

		// Restore one cell in each row this cell was covering vertically.
		for ( let r = 1; r < rowSpan; r++ ) {
			const belowRow = ctx.rowOrder[ ctx.rowIndex + r ];
			if ( belowRow ) {
				const at = Math.min(
					ctx.colIndex,
					ctx.sel.getBlockOrder( belowRow ).length
				);
				dispatch.insertBlocks(
					createTableCellBlock( { tag: 'td' } ),
					at,
					belowRow,
					false
				);
			}
		}
	}, [ registry, resolve, cellClientId ] );

	return {
		insertRow,
		deleteRow,
		insertColumn,
		deleteColumn,
		mergeRight,
		mergeDown,
		splitCell,
	};
}
