/**
 * Reusable box border control for style-schema `boxBorder` fields.
 * Mini popup: style, linked width (1 / 2 / 4), color, and per-side on/off toggles.
 */
import {
	BaseControl,
	Button,
	ColorIndicator,
	Dropdown,
	SelectControl,
	__experimentalDropdownContentWrapper as DropdownContentWrapper,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { link, linkOff } from '@wordpress/icons';
import { useThemeTokens } from '../hooks/useThemeTokens';
import {
	BORDER_STYLE_OPTIONS,
	DEFAULT_BORDER_COLOR,
	DEFAULT_BORDER_STYLE,
	DEFAULT_BORDER_WIDTH,
	borderSideWidth,
	boxBorderKeys,
	isBorderSideOn,
	resolveBoxBorderState,
} from '../styleFieldBorder';
import FlexibleUnitControl from './FlexibleUnitControl';
import ThemeColorPalette from './ThemeColorPalette';
import {
	COLOR_DROPDOWN_POPOVER_SELECTOR,
	colorDropdownPopoverProps,
	useColorDropdownOutsideClose,
	usePopoverOutsideClose,
} from './colorDropdownFocus';

const SIDE_DEFS = [
	{ id: 'top', label: __( 'Top', 'joc-blockora-morph' ), short: 'T' },
	{ id: 'right', label: __( 'Right', 'joc-blockora-morph' ), short: 'R' },
	{ id: 'bottom', label: __( 'Bottom', 'joc-blockora-morph' ), short: 'B' },
	{ id: 'left', label: __( 'Left', 'joc-blockora-morph' ), short: 'L' },
];

const WIDTH_MODES = [ 'linked', 'axial', 'sides' ];

const BORDER_POPOVER_KEEP_OPEN = [
	'.bm-box-border__popover',
	COLOR_DROPDOWN_POPOVER_SELECTOR,
].join( ', ' );

const WIDTH_UNITS = [
	{ value: 'px', label: 'px', default: 1 },
	{ value: 'em', label: 'em', default: 0.1 },
	{ value: 'rem', label: 'rem', default: 0.1 },
];

/**
 * @param {object}   props
 * @param {object}   props.field
 * @param {object}   [props.values]
 * @param {Function} props.onChange  (patch: Record<string, string>) => void
 */
export default function BoxBorderField( { field, values = {}, onChange } ) {
	const { colorPalettes } = useThemeTokens();
	const [ open, setOpen ] = useState( false );
	const [ colorOpen, setColorOpen ] = useState( false );
	const close = () => {
		setColorOpen( false );
		setOpen( false );
	};
	const closeColor = () => setColorOpen( false );
	const hostRef = usePopoverOutsideClose(
		open,
		close,
		BORDER_POPOVER_KEEP_OPEN
	);
	const colorHostRef = useColorDropdownOutsideClose( colorOpen, closeColor );
	const keys = boxBorderKeys( field );
	const fixedSides = Array.isArray( field?.fixedSides )
		? field.fixedSides.map( ( s ) => String( s ).toLowerCase() )
		: null;
	const isFixedBottomOnly =
		fixedSides?.length === 1 && fixedSides[ 0 ] === 'bottom';
	const {
		style,
		color,
		masterWidth,
		sidesOn,
		sideWidths,
		draftWidth,
		linkedWidth,
		axialY,
		axialX,
		widthMode,
	} = resolveBoxBorderState( values, keys, field );

	const setOpenAndSync = ( nextOpen ) => {
		const next =
			typeof nextOpen === 'function' ? nextOpen( open ) : nextOpen;
		setOpen( next );
	};

	const fallbackWidth = field.defaultWidth ?? DEFAULT_BORDER_WIDTH;
	const fallbackStyle = field.defaultStyle ?? DEFAULT_BORDER_STYLE;
	const fallbackColor = field.defaultColor ?? DEFAULT_BORDER_COLOR;

	const emit = ( next = {} ) => {
		const s = next.style != null ? next.style : style;
		const c = next.color != null ? next.color : color;
		const nextMode =
			next.widthMode != null ? next.widthMode : widthMode;
		const nextOn = {
			top: next.topOn != null ? next.topOn : sidesOn.top,
			right: next.rightOn != null ? next.rightOn : sidesOn.right,
			bottom: next.bottomOn != null ? next.bottomOn : sidesOn.bottom,
			left: next.leftOn != null ? next.leftOn : sidesOn.left,
		};
		const widthFor = ( side, explicit ) => {
			if ( explicit != null ) {
				return explicit;
			}
			if ( nextOn[ side ] ) {
				const raw = sideWidths[ side ];
				return isBorderSideOn( raw ) ? raw : draftWidth;
			}
			return '0';
		};
		const nextWidths = {
			top: widthFor( 'top', next.topWidth ),
			right: widthFor( 'right', next.rightWidth ),
			bottom: widthFor( 'bottom', next.bottomWidth ),
			left: widthFor( 'left', next.leftWidth ),
		};

		// Width edits with every side off used to keep writing 0 — turn sides on.
		const widthTouched =
			next.width != null ||
			next.topWidth != null ||
			next.rightWidth != null ||
			next.bottomWidth != null ||
			next.leftWidth != null;
		const anySideOn =
			nextOn.top || nextOn.right || nextOn.bottom || nextOn.left;
		if ( widthTouched && ! anySideOn ) {
			[ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
				const key = `${ side }Width`;
				if (
					next[ key ] != null ||
					next.width != null ||
					isBorderSideOn( nextWidths[ side ] )
				) {
					nextOn[ side ] = isBorderSideOn( nextWidths[ side ] );
				}
			} );
			if (
				! nextOn.top &&
				! nextOn.right &&
				! nextOn.bottom &&
				! nextOn.left &&
				isBorderSideOn(
					next.width != null ? next.width : nextWidths.top
				)
			) {
				nextOn.top = true;
				nextOn.right = true;
				nextOn.bottom = true;
				nextOn.left = true;
			}
		}

		const onMaster =
			[ 'top', 'right', 'bottom', 'left' ]
				.filter( ( side ) => nextOn[ side ] )
				.map( ( side ) => nextWidths[ side ] )
				.find( ( w ) => isBorderSideOn( w ) ) || '';
		const master =
			next.width != null
				? next.width
				: onMaster ||
				  ( isBorderSideOn( masterWidth ) ? masterWidth : '' ) ||
				  fallbackWidth;

		if ( fixedSides?.length ) {
			[ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
				if ( ! fixedSides.includes( side ) ) {
					nextOn[ side ] = false;
					nextWidths[ side ] = '0';
				}
			} );
			if ( widthTouched ) {
				fixedSides.forEach( ( side ) => {
					if ( isBorderSideOn( nextWidths[ side ] ) || isBorderSideOn( master ) ) {
						nextOn[ side ] = true;
						if ( ! isBorderSideOn( nextWidths[ side ] ) ) {
							nextWidths[ side ] =
								( isBorderSideOn( master ) && master ) ||
								draftWidth ||
								fallbackWidth;
						}
					}
				} );
			}
		}

		onChange( {
			[ keys.width ]: master || fallbackWidth,
			[ keys.style ]: s || fallbackStyle,
			[ keys.color ]: c || fallbackColor,
			[ keys.top ]: borderSideWidth(
				nextOn.top,
				nextWidths.top,
				draftWidth
			),
			[ keys.right ]: borderSideWidth(
				nextOn.right,
				nextWidths.right,
				draftWidth
			),
			[ keys.bottom ]: borderSideWidth(
				nextOn.bottom,
				nextWidths.bottom,
				draftWidth
			),
			[ keys.left ]: borderSideWidth(
				nextOn.left,
				nextWidths.left,
				draftWidth
			),
			[ keys.widthMode ]: fixedSides?.length ? 'sides' : nextMode,
		} );
	};

	const writeLinkedWidth = ( next ) => {
		const w = next || fallbackWidth;
		const on = isBorderSideOn( w );
		if ( isFixedBottomOnly ) {
			emit( {
				width: w,
				topWidth: '0',
				rightWidth: '0',
				bottomWidth: w,
				leftWidth: '0',
				topOn: false,
				rightOn: false,
				bottomOn: on,
				leftOn: false,
				widthMode: 'sides',
			} );
			return;
		}
		emit( {
			width: w,
			topWidth: w,
			rightWidth: w,
			bottomWidth: w,
			leftWidth: w,
			topOn: on,
			rightOn: on,
			bottomOn: on,
			leftOn: on,
			widthMode: 'linked',
		} );
	};

	const writeAxialWidth = ( vertical, horizontal ) => {
		// Keep explicit 0 — do not promote an off axis to the fallback width.
		const y =
			vertical != null && String( vertical ).trim() !== ''
				? String( vertical ).trim()
				: '0';
		const x =
			horizontal != null && String( horizontal ).trim() !== ''
				? String( horizontal ).trim()
				: '0';
		emit( {
			width: isBorderSideOn( y )
				? y
				: isBorderSideOn( x )
				? x
				: fallbackWidth,
			topWidth: y,
			bottomWidth: y,
			leftWidth: x,
			rightWidth: x,
			topOn: isBorderSideOn( y ),
			bottomOn: isBorderSideOn( y ),
			leftOn: isBorderSideOn( x ),
			rightOn: isBorderSideOn( x ),
			widthMode: 'axial',
		} );
	};

	const cycleWidthMode = () => {
		const idx = WIDTH_MODES.indexOf( widthMode );
		const next =
			WIDTH_MODES[ ( idx < 0 ? 0 : idx + 1 ) % WIDTH_MODES.length ];

		if ( next === 'linked' ) {
			writeLinkedWidth( linkedWidth || draftWidth || masterWidth );
			return;
		}
		// Axial / sides: keep current side values — only persist the mode.
		// Rewriting axial used to copy the vertical width onto left/right.
		emit( { widthMode: next } );
	};

	const previewStyle = {
		borderStyle: style === 'none' ? 'solid' : style,
		borderColor: color,
		borderTopWidth: sidesOn.top ? sideWidths.top : '0',
		borderRightWidth: sidesOn.right ? sideWidths.right : '0',
		borderBottomWidth: sidesOn.bottom ? sideWidths.bottom : '0',
		borderLeftWidth: sidesOn.left ? sideWidths.left : '0',
	};

	const sideSummary = isFixedBottomOnly
		? sidesOn.bottom
			? 'B'
			: ''
		: SIDE_DEFS.filter( ( side ) => sidesOn[ side.id ] )
				.map( ( side ) => side.short )
				.join( '' );

	const widthSummary = () => {
		if ( isFixedBottomOnly ) {
			return sidesOn.bottom ? sideWidths.bottom : '0';
		}
		if ( widthMode === 'linked' ) {
			return linkedWidth;
		}
		if ( widthMode === 'axial' ) {
			return axialY === axialX ? axialY : `${ axialY } / ${ axialX }`;
		}
		return [ 'top', 'right', 'bottom', 'left' ]
			.map( ( id ) => ( sidesOn[ id ] ? sideWidths[ id ] : '0' ) )
			.join( ' ' );
	};

	const summary =
		style === 'none'
			? __( 'None', 'joc-blockora-morph' )
			: `${ style } · ${ widthSummary() }${
					sideSummary ? ` · ${ sideSummary }` : ''
			  }`;

	const styleOptions = BORDER_STYLE_OPTIONS.map( ( opt ) => ( {
		...opt,
		label:
			opt.value === 'solid'
				? __( 'Solid', 'joc-blockora-morph' )
				: opt.value === 'dashed'
				? __( 'Dashed', 'joc-blockora-morph' )
				: opt.value === 'dotted'
				? __( 'Dotted', 'joc-blockora-morph' )
				: opt.value === 'double'
				? __( 'Double', 'joc-blockora-morph' )
				: __( 'None', 'joc-blockora-morph' ),
	} ) );

	const widthTip =
		widthMode === 'linked'
			? __(
					'Linked: one width for all sides. Click for top↔bottom / left↔right.',
					'joc-blockora-morph'
			  )
			: widthMode === 'axial'
			? __(
					'Paired: top↔bottom and left↔right. Click to edit every side.',
					'joc-blockora-morph'
			  )
			: __(
					'Unlinked: each side has its own width. Click to link all sides.',
					'joc-blockora-morph'
			  );

	const renderWidthField = ( label, value, onValueChange ) => (
		<FlexibleUnitControl
			label={ label }
			value={ value }
			onChange={ ( next ) =>
				onValueChange( next || fallbackWidth )
			}
			units={ WIDTH_UNITS }
		/>
	);

	return (
		<div
			ref={ hostRef }
			className="bm-style-field bm-style-field--box-border bm-box-border"
		>
			<span className="bm-box-border__label">
				{ field.label || __( 'Border', 'joc-blockora-morph' ) }
			</span>
			<Dropdown
				open={ open }
				onToggle={ setOpenAndSync }
				onClose={ close }
				className="bm-box-border__dropdown"
				popoverProps={ {
					placement: 'bottom-start',
					offset: 8,
					focusOnMount: false,
					onFocusOutside: ( event ) => {
						const path =
							typeof event?.composedPath === 'function'
								? event.composedPath()
								: [];
						const nodes = path.length
							? path
							: [ event?.target ];
						for ( let i = 0; i < nodes.length; i++ ) {
							const node = nodes[ i ];
							if (
								node?.nodeType === 1 &&
								node.closest?.( BORDER_POPOVER_KEEP_OPEN )
							) {
								return;
							}
						}
						close();
					},
				} }
				renderToggle={ ( { isOpen, onToggle } ) => (
					<button
						type="button"
						className={
							'bm-box-border__toggle' + ( isOpen ? ' is-open' : '' )
						}
						onClick={ onToggle }
						aria-expanded={ isOpen }
						aria-label={ __( 'Edit border', 'joc-blockora-morph' ) }
					>
						<span
							className="bm-box-border__preview"
							style={ previewStyle }
							aria-hidden="true"
						/>
						<span className="bm-box-border__meta">
							<span className="bm-box-border__meta-title">
								{ __( 'Border', 'joc-blockora-morph' ) }
							</span>
							<span className="bm-box-border__meta-sub">
								{ summary }
							</span>
						</span>
						<ColorIndicator colorValue={ color } />
					</button>
				) }
				renderContent={ () => (
					<DropdownContentWrapper paddingSize="medium">
						<div
							className={
								'bm-box-border__popover' +
								` bm-box-border__popover--width-${ widthMode }`
							}
						>
							<p className="bm-box-border__popover-title">
								{ field.label ||
									__( 'Border', 'joc-blockora-morph' ) }
							</p>
							<SelectControl
								label={ __( 'Style', 'joc-blockora-morph' ) }
								value={ style }
								options={ styleOptions }
								onChange={ ( next ) =>
									emit( { style: next } )
								}
								__next40pxDefaultSize
								__nextHasNoMarginBottom
							/>

							<div className="bm-box-border__width">
								<div className="bm-box-border__width-toolbar">
									<strong className="bm-box-border__width-title">
										{ isFixedBottomOnly
											? __(
													'Bottom width',
													'joc-blockora-morph'
											  )
											: __( 'Width', 'joc-blockora-morph' ) }
									</strong>
									{ isFixedBottomOnly ? null : (
										<Button
											className={
												'component-box-control__linked-button bm-box-border__width-link' +
												( widthMode === 'linked'
													? ' is-linked'
													: '' ) +
												( widthMode === 'axial'
													? ' is-axial'
													: '' )
											}
											size="small"
											icon={
												widthMode === 'linked'
													? link
													: linkOff
											}
											iconSize={ 24 }
											onClick={ cycleWidthMode }
											aria-pressed={
												widthMode === 'linked'
											}
											label={ widthTip }
											showTooltip
										/>
									) }
								</div>

								{ isFixedBottomOnly || widthMode === 'linked'
									? renderWidthField(
											isFixedBottomOnly
												? __(
														'Bottom',
														'joc-blockora-morph'
												  )
												: __(
														'Width',
														'joc-blockora-morph'
												  ),
											isFixedBottomOnly
												? sideWidths.bottom ||
														linkedWidth
												: linkedWidth,
											writeLinkedWidth
									  )
									: null }

								{ ! isFixedBottomOnly && widthMode === 'axial' ? (
									<div className="bm-box-border__width-grid">
										{ renderWidthField(
											__(
												'Top / Bottom',
												'joc-blockora-morph'
											),
											axialY,
											( next ) =>
												writeAxialWidth( next, axialX )
										) }
										{ renderWidthField(
											__(
												'Left / Right',
												'joc-blockora-morph'
											),
											axialX,
											( next ) =>
												writeAxialWidth( axialY, next )
										) }
									</div>
								) : null }

								{ ! isFixedBottomOnly && widthMode === 'sides' ? (
									<div className="bm-box-border__width-grid">
										{ renderWidthField(
											__( 'Top', 'joc-blockora-morph' ),
											sideWidths.top,
											( next ) =>
												emit( {
													topWidth: next,
													topOn: isBorderSideOn(
														next
													),
													widthMode: 'sides',
												} )
										) }
										{ renderWidthField(
											__( 'Right', 'joc-blockora-morph' ),
											sideWidths.right,
											( next ) =>
												emit( {
													rightWidth: next,
													rightOn: isBorderSideOn(
														next
													),
													widthMode: 'sides',
												} )
										) }
										{ renderWidthField(
											__( 'Bottom', 'joc-blockora-morph' ),
											sideWidths.bottom,
											( next ) =>
												emit( {
													bottomWidth: next,
													bottomOn: isBorderSideOn(
														next
													),
													widthMode: 'sides',
												} )
										) }
										{ renderWidthField(
											__( 'Left', 'joc-blockora-morph' ),
											sideWidths.left,
											( next ) =>
												emit( {
													leftWidth: next,
													leftOn: isBorderSideOn(
														next
													),
													widthMode: 'sides',
												} )
										) }
									</div>
								) : null }
							</div>

							<div className="bm-box-border__color-row">
								<span className="bm-box-border__color-label">
									{ __( 'Color', 'joc-blockora-morph' ) }
								</span>
								<div
									ref={ colorHostRef }
									className="bm-color-dropdown-host"
								>
									<Dropdown
										open={ colorOpen }
										onToggle={ setColorOpen }
										onClose={ closeColor }
										className="bm-color-dropdown"
										contentClassName="bm-color-dropdown__content"
										popoverProps={ colorDropdownPopoverProps(
											closeColor
										) }
										renderToggle={ ( {
											isOpen,
											onToggle,
										} ) => (
											<Button
												className="bm-color-dropdown__toggle"
												onClick={ onToggle }
												aria-expanded={ isOpen }
												variant="tertiary"
											>
												<ColorIndicator
													colorValue={ color }
												/>
												<span>
													{ color ||
														__(
															'None',
															'joc-blockora-morph'
														) }
												</span>
											</Button>
										) }
										renderContent={ () => (
											<DropdownContentWrapper paddingSize="medium">
												<div className="bm-color-dropdown__panel">
													<ThemeColorPalette
														palettes={
															colorPalettes
														}
														value={ color || '' }
														onChange={ ( next ) =>
															emit( {
																color:
																	next ==
																		null ||
																	String(
																		next
																	).trim() ===
																		''
																		? fallbackColor
																		: next,
															} )
														}
														clearable
														enableAlpha
													/>
												</div>
											</DropdownContentWrapper>
										) }
									/>
								</div>
							</div>
							{ isFixedBottomOnly ? null : (
								<BaseControl
									label={ __( 'Sides', 'joc-blockora-morph' ) }
									__nextHasNoMarginBottom
								>
									<div
										className="bm-box-sides-pad bm-box-sides-pad--edges"
										role="group"
										aria-label={ __(
											'Border sides',
											'joc-blockora-morph'
										) }
									>
										{ SIDE_DEFS.map( ( side ) => (
											<button
												key={ side.id }
												type="button"
												className={
													`bm-box-sides-pad__cell bm-box-sides-pad__cell--${ side.id }` +
													( sidesOn[ side.id ]
														? ' is-on'
														: '' )
												}
												aria-pressed={
													sidesOn[ side.id ]
												}
												aria-label={ side.label }
												onClick={ () => {
													const turningOn =
														! sidesOn[ side.id ];
													emit( {
														[ `${ side.id }On` ]:
															turningOn,
														...( turningOn
															? {
																	[ `${ side.id }Width` ]:
																		draftWidth,
															  }
															: {} ),
														widthMode: 'sides',
													} );
												} }
											>
												{ side.short }
											</button>
										) ) }
										<span
											className="bm-box-sides-pad__preview"
											style={ previewStyle }
											aria-hidden="true"
										/>
									</div>
								</BaseControl>
							) }
						</div>
					</DropdownContentWrapper>
				) }
			/>
		</div>
	);
}
