import { useWatch } from 'react-hook-form';
import { applyFilters } from '@wordpress/hooks';
import { CONTROLS } from './controls';
import { SettingRow } from './components/SettingRow';
import { ProBanner } from '@admin/components/ProBanner';
import { ManualLink } from '@admin/components/ManualLink';

function buildNote( field ) {
	if ( ! field.manual_url && ! field.note ) {
		return null;
	}

	return (
		<>
			{ field.manual_url && (
				<>
					{ ' ' }
					<ManualLink href={ field.manual_url } />
				</>
			) }
			{ field.note && (
				<span
					dangerouslySetInnerHTML={ { __html: ' ' + field.note } }
				/>
			) }
		</>
	);
}

function evaluateDependencies( dependencies, watchedFieldValues, context ) {
	if ( ! dependencies || 0 === dependencies.length ) {
		return true;
	}

	return dependencies.every( ( condition ) => {
		const actual =
			'system' === condition.source
				? context?.[ condition.key ]
				: watchedFieldValues[ condition.field ];

		return actual === condition.value;
	} );
}

function useDependencyState( field, context ) {
	const referenced = ( field.dependencies ?? [] )
		.filter( ( c ) => 'field' === c.source )
		.map( ( c ) => c.field );

	const values = useWatch( { name: referenced } );
	const watchedFieldValues = Object.fromEntries(
		referenced.map( ( name, i ) => [ name, values[ i ] ] )
	);

	return evaluateDependencies(
		field.dependencies,
		watchedFieldValues,
		context
	);
}

function LeafField( { field, context, parentDisabled, highlightId } ) {
	const met = useDependencyState( field, context );
	const effect = field.effect ?? 'disable';

	if ( ! met && 'hide' === effect ) {
		return null;
	}

	const disabled =
		parentDisabled ||
		field.locked ||
		field.disabled ||
		( ! met && 'disable' === effect );
	const highlighted = field.id === highlightId;
	const Control = CONTROLS[ field.type ];
	const resolvedField = { ...field, disabled };
	const noLabel = 'checkbox' === field.type || 'raw' === field.type;

	// Lets a module render a real React component in place of any field —
	// most commonly a `raw` field's default dangerouslySetInnerHTML, for
	// icons or interactivity a static HTML string can't provide — by
	// hooking the field's id:
	// `addFilter('advancedAds.settingsField', 'my-module/my-field', (element, field) => field.id === 'my-field-id' ? <MyComponent field={field} /> : element)`
	const renderedControl = applyFilters(
		'advancedAds.settingsField',
		<Control field={ resolvedField } />,
		resolvedField,
		context
	);

	const control = (
		<>
			{ renderedControl }
			{ field.locked && (
				<ProBanner
					text={ field.lock_message }
					upgradeUrl={ field.upgrade_url }
				/>
			) }
		</>
	);

	return (
		<SettingRow
			layout={ field.layout || 'row' }
			id={ field.id }
			label={ noLabel ? null : field.label }
			description={ field.description }
			note={ buildNote( field ) }
			disabled={ disabled }
			highlighted={ highlighted }
			className={ field.className }
		>
			{ control }
		</SettingRow>
	);
}

function GroupField( { field, context, parentDisabled, highlightId } ) {
	const met = useDependencyState( field, context );
	// Groups default to 'hide' (their historical behavior) rather than
	// 'disable' — the opposite default from leaf fields — since most
	// groups have no `effect` set at all and previously always hid.
	const effect = field.effect ?? 'hide';

	if ( ! met && 'hide' === effect ) {
		return null;
	}

	const disabled = parentDisabled || ( ! met && 'disable' === effect );

	return (
		<SettingRow
			layout="block"
			id={ field.id }
			label={ field.label }
			description={ field.description }
			note={ buildNote( field ) }
			disabled={ disabled }
			highlighted={ field.id === highlightId }
			className={ field.className }
		>
			{ field.children.map( ( child ) => {
				child.layout = 'nested';

				return (
					<FieldRenderer
						key={ child.id }
						field={ child }
						context={ context }
						parentDisabled={ disabled }
						highlightId={ highlightId }
					/>
				);
			} ) }
		</SettingRow>
	);
}

/**
 * Recursively render one schema row: a leaf field, or a group of fields.
 *
 * @param {Object}  props
 * @param {Object}  props.field            Field or group definition.
 * @param {Object}  props.context          Fetched system-level context map.
 * @param {boolean} [props.parentDisabled] Whether an ancestor group is
 *                                         disabled (effect: 'disable' with
 *                                         unmet dependencies) — forces every
 *                                         descendant leaf field disabled too.
 * @param {string}  [props.highlightId]    Id of the field to briefly
 *                                         highlight (set after a search
 *                                         result is clicked), compared
 *                                         against each field's own id.
 */
export function FieldRenderer( {
	field,
	context,
	parentDisabled,
	highlightId,
} ) {
	if ( 'group' === field.type ) {
		return (
			<GroupField
				field={ field }
				context={ context }
				parentDisabled={ parentDisabled }
				highlightId={ highlightId }
			/>
		);
	}

	return (
		<LeafField
			field={ field }
			context={ context }
			parentDisabled={ parentDisabled }
			highlightId={ highlightId }
		/>
	);
}
