import { clsx } from '@admin/utils';
import { Fragment, useEffect, useState } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

import { STORE_NAME } from '@admin/store';
import { fetchSettings, saveSettings } from './hooks/settings-api';
import { useScrollHighlight } from './hooks/useScrollHighlight';
import { buildZodSchema } from './validation';
import { walkFields } from './schema-utils';
import { FieldRenderer } from './FieldRenderer';
import { runCommand, showCommandNotice } from './controls/runCommand';
import { SectionCaption } from '@admin/components/SectionCaption';
import { Card } from '@admin/components/Card';
import { PageHeader } from './components/PageHeader';
import { SettingsHeaderActions } from './components/SettingsHeaderActions';
import { SettingsSidebarNav } from './components/SettingsSidebarNav';
import { ModulesTab } from './components/ModulesTab';
import { UpsellPage } from './components/UpsellPage';
import { UPSELL_TABS } from './upsell-tabs';

// Side-effect only: registers this module's `advancedAds.settingsRawField`
// filter (see controls/RawField.jsx) so its raw field mounts a real React
// component instead of the default dangerouslySetInnerHTML.
import './raw-fields/adsense-account-overview';

function collectFieldIds( tab ) {
	const ids = [];

	tab.children.forEach( ( group ) =>
		walkFields( group.children, ( row ) => ids.push( row.id ) )
	);

	return ids;
}

export function Settings() {
	const dispatch = useDispatch( STORE_NAME );
	const { tabs, values, context } = useSelect(
		( select ) => ( {
			tabs: select( STORE_NAME ).getSettingsSchema(),
			values: select( STORE_NAME ).getSettingsValues(),
			context: select( STORE_NAME ).getSettingsContext(),
		} ),
		[]
	);

	const [ activeTab, setActiveTab ] = useState( null );
	const [ loaded, setLoaded ] = useState( false );
	const [ justSaved, setJustSaved ] = useState( false );
	const { highlightId, scrollTo } = useScrollHighlight( activeTab );

	const methods = useForm( {
		defaultValues: values,
		resolver: loaded
			? zodResolver( buildZodSchema( { tabs } ) )
			: undefined,
	} );

	useEffect( () => {
		fetchSettings().then( ( schema ) => {
			dispatch.setSettingsSchema(
				schema.tabs,
				schema.values,
				schema.context
			);
			methods.reset( schema.values );
			const params = new URLSearchParams( window.location.search );
			const requested = params.get( 'tab' );
			setActiveTab(
				schema.tabs.find( ( t ) => t.key === requested )?.key ??
					schema.tabs[ 0 ]?.key ??
					null
			);
			setLoaded( true );
		} );
		// eslint-disable-next-line react-hooks/exhaustive-deps -- run once on mount.
	}, [] );

	// A tab can vanish (a module got deactivated elsewhere, or a bookmarked
	// ?tab= URL predates a change) — fall back to the first available tab
	// instead of rendering a blank page.
	useEffect( () => {
		if (
			loaded &&
			tabs.length &&
			! tabs.find( ( tab ) => tab.key === activeTab )
		) {
			setActiveTab( tabs[ 0 ].key );
		}
	}, [ loaded, tabs, activeTab ] );

	if ( ! loaded ) {
		return null;
	}

	const currentTab = tabs.find( ( tab ) => tab.key === activeTab );
	const upsell = UPSELL_TABS[ activeTab ];
	const showUpsell =
		Boolean( upsell ) && ! context[ upsell.activeContextKey ];

	const onNavigate = ( tabKey, fieldId ) => {
		setActiveTab( tabKey );
		scrollTo( fieldId ?? null );
	};

	// Always refetches the schema after saving, not just values — activating
	// or deactivating a module (via the same submit, see ActiveModuleCard)
	// can change which tabs exist, and the schema is only ever rebuilt fresh
	// on a new request, not from the save response itself.
	const onSave = methods.handleSubmit( async ( formValues ) => {
		await saveSettings( formValues );
		const schema = await fetchSettings();
		dispatch.setSettingsSchema(
			schema.tabs,
			schema.values,
			schema.context
		);
		methods.reset( schema.values );
		setJustSaved( true );
		setTimeout( () => setJustSaved( false ), 2500 );
	} );

	const onReset = () => {
		collectFieldIds( currentTab ).forEach( ( id ) => {
			methods.resetField( id, { defaultValue: values[ id ] } );
		} );
	};

	// Delegated at the screen root (bubbling, not a per-field listener): any
	// element anywhere in the tree — most commonly inside a raw field's
	// rendered HTML — carrying data-command/data-action/data-* triggers a
	// REST or admin-ajax request. See controls/runCommand.js.
	const onCommandClick = ( event ) => {
		const el = event.target.closest( '[data-command]' );
		if ( ! el ) {
			return;
		}

		el.disabled = true;
		runCommand( el )
			.then( ( result ) => {
				showCommandNotice( el, result );
				if ( result.success && el.dataset.reload ) {
					window.location.reload();
				}
			} )
			.finally( () => {
				el.disabled = false;
			} );
	};

	let tabContent;
	if ( showUpsell ) {
		tabContent = (
			<UpsellPage
				tabLabel={ currentTab?.label }
				title={ upsell.title }
				subtitle={ upsell.subtitle }
				features={ upsell.features }
				ctaUrl={ context[ upsell.urlContextKey ] }
				allAccessUrl={ context.all_access_url }
				previewGroups={ upsell.previewGroups }
			/>
		);
	} else if ( 'modules' === activeTab ) {
		tabContent = <ModulesTab tab={ currentTab } onSave={ onSave } />;
	} else {
		tabContent = currentTab?.children.map( ( group ) => {
			const fields = group.children.map( ( row ) => (
				<FieldRenderer
					key={ row.id }
					field={ row }
					context={ context }
					highlightId={ highlightId }
				/>
			) );

			return (
				<Fragment key={ group.id }>
					{ group.label && (
						<SectionCaption>{ group.label }</SectionCaption>
					) }
					{ 'bare' === group.layout ? (
						fields
					) : (
						<Card className={ clsx( group.className, 'p-0' ) }>
							{ fields }
						</Card>
					) }
				</Fragment>
			);
		} );
	}

	return (
		<FormProvider { ...methods }>
			{ /* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- click delegation wrapper; the actual [data-command] controls are focusable/keyboard-operable on their own */ }
			<div onClick={ onCommandClick }>
				<SettingsHeaderActions
					tabs={ tabs }
					onNavigate={ onNavigate }
					isDirty={ methods.formState.isDirty }
					justSaved={ justSaved }
					onSave={ onSave }
				/>
				<div className="flex bg-bg-app">
					<SettingsSidebarNav
						tabs={ tabs }
						activeTab={ activeTab }
						onSelect={ setActiveTab }
					/>
					<div className="max-w-4xl min-w-0 flex-1 py-6 pl-6">
						<PageHeader
							title={ currentTab?.label }
							onReset={ showUpsell ? undefined : onReset }
						/>
						{ tabContent }
					</div>
				</div>
			</div>
		</FormProvider>
	);
}
