/**
 * WordPress dependencies
 */
import { Button, CheckboxControl } from '@safe-wordpress/components';
import { useDispatch, useSelect } from '@safe-wordpress/data';
import {
	createInterpolateElement,
	useEffect,
	useState,
} from '@safe-wordpress/element';
import { _x } from '@safe-wordpress/i18n';

/**
 * External dependencies
 */
import clsx from 'clsx';
import { HelpIcon } from '@nab/components';
import { store as NAB_EXPERIMENTS } from '@nab/experiments';
import {
	createAlternative,
	duplicateAlternative,
	getLetter,
	omitUndefineds,
} from '@nab/utils';
import type {
	Alternative,
	AlternativeCustomAction,
	AlternativeId,
	Dict,
	ExperimentEditProps,
	Maybe,
} from '@nab/types';

/**
 * Internal dependencies
 */
import { useCanAddAlternatives, useExperimentAttribute } from '../hooks';
import { store as NAB_EDITOR } from '../../store';

export type AlternativeContainerProps = {
	readonly alternativeId: AlternativeId;
	readonly disabled?: boolean;
	readonly index: number;
};

export const AlternativeContainer = ( {
	alternativeId,
	disabled,
	index,
}: AlternativeContainerProps ): JSX.Element | null => {
	const control = useAlternative( 'control' );
	const alternative = useAlternative( alternativeId );
	const AlternativeView = useView( index );
	const canBeRemoved = useCanBeRemoved();
	const experimentType = useExperimentType();
	const help = useHelp( index );
	const isBeingSaved = useIsBeingSaved();
	const isExternalEdit = useIsExternalEdit();
	const isControl = ! index;
	const canAddAlternatives = useCanAddAlternatives();
	const duplicate = useDuplicator( alternative );
	const isPreviewDisabled = useIsPreviewDisabled( alternative, control );
	const customActions = useCustomActions( alternative, control );

	const {
		saveExperimentAndEditAlternative,
		saveExperimentAndPreviewAlternative,
		removeAlternatives,
		replaceAlternatives,
		setAlternative,
		showAiAlternativeModal,
	} = useDispatch( NAB_EDITOR );

	if ( ! alternative || ! control ) {
		return null;
	}

	const setAttributes = ( attributes: Dict ) =>
		setAlternative( alternative.id, {
			...alternative,
			attributes: omitUndefineds( {
				...alternative.attributes,
				...attributes,
			} ),
		} );

	const resetAlternative = () =>
		replaceAlternatives( alternativeId, {
			...createAlternative(),
			attributes: {
				name: alternative.ai?.name || '',
				chance: alternative.attributes.chance,
			},
			ai: alternative.ai
				? { ...alternative.ai, isReady: false }
				: undefined,
		} );

	return (
		<div
			className={ clsx( [
				'nab-alternative-list__alternative',
				{ 'nab-alternative-list__alternative--original': isControl },
			] ) }
		>
			<p
				className={ clsx( [
					'nab-alternative-list__alternative-letter',
					{
						'nab-alternative-list__alternative-letter--disabled':
							0 < index && disabled,
					},
				] ) }
			>
				{ getLetter( index ) }
			</p>

			<div className="nab-alternative-list__alternative-edit">
				<AlternativeView
					attributes={ alternative.attributes }
					disabled={ disabled }
					experimentType={ experimentType }
					setAttributes={ setAttributes }
					ai={ alternative.ai }
				/>

				{ !! alternative.ai && (
					<div className="nab-alternative-list__alternative-ai-changes">
						<CheckboxControl
							checked={ alternative.ai.isReady }
							onChange={ ( isReady ) =>
								setAlternative( alternative.id, {
									...alternative,
									ai: alternative.ai
										? { ...alternative.ai, isReady }
										: undefined,
								} )
							}
							label={ createInterpolateElement(
								_x(
									'I’ve already applied all <a>Nelio AI’s recommended changes</a> to this variant',
									'text',
									'nelio-ab-testing'
								),
								{
									a: (
										<Button
											variant="link"
											disabled={
												disabled || isBeingSaved
											}
											onClick={ () =>
												showAiAlternativeModal(
													alternative.id
												)
											}
										/>
									),
								}
							) }
						/>
					</div>
				) }

				{ 0 !== index && (
					<ul className="nab-alternative-list__alternative-actions">
						{ customActions.map( ( action ) =>
							! action.isVisible ||
							action.isVisible(
								alternative.attributes,
								control.attributes
							) ? (
								<li key={ `custom-action-${ action.id }` }>
									<Button
										variant="link"
										disabled={
											disabled ||
											isBeingSaved ||
											( action.isEnabled &&
												! action.isEnabled(
													alternative.attributes,
													control.attributes
												) )
										}
										onClick={ () =>
											action.onClick(
												alternative.attributes,
												setAttributes,
												control.attributes
											)
										}
									>
										{ action.label }
									</Button>
								</li>
							) : null
						) }
						{ isExternalEdit && (
							<li>
								<Button
									variant="link"
									disabled={ disabled || isBeingSaved }
									onClick={ () =>
										void saveExperimentAndEditAlternative(
											alternativeId
										)
									}
								>
									{ _x(
										'Edit',
										'command',
										'nelio-ab-testing'
									) }
								</Button>
							</li>
						) }
						<li>
							<Button
								variant="link"
								disabled={
									disabled ||
									isBeingSaved ||
									isPreviewDisabled
								}
								onClick={ () =>
									void saveExperimentAndPreviewAlternative(
										alternativeId
									)
								}
							>
								{ _x(
									'Preview',
									'command',
									'nelio-ab-testing'
								) }
							</Button>
						</li>
						{ isExternalEdit &&
							! alternative.base &&
							canAddAlternatives && (
								<li>
									<Button
										variant="link"
										disabled={ disabled || isBeingSaved }
										onClick={ duplicate }
									>
										{ _x(
											'Duplicate',
											'command',
											'nelio-ab-testing'
										) }
									</Button>
								</li>
							) }
						{ isExternalEdit && (
							<li>
								<Button
									variant="link"
									disabled={ disabled || isBeingSaved }
									onClick={ resetAlternative }
								>
									{ _x(
										'Reset',
										'command',
										'nelio-ab-testing'
									) }
								</Button>
							</li>
						) }
						{ canBeRemoved && (
							<li>
								<Button
									variant="link"
									isDestructive
									disabled={ disabled || isBeingSaved }
									onClick={ () =>
										removeAlternatives( alternative.id )
									}
								>
									{ _x(
										'Delete',
										'command',
										'nelio-ab-testing'
									) }
								</Button>
							</li>
						) }
					</ul>
				) }
			</div>

			<div className="nab-alternative-list__alternative-help">
				{ !! help && <HelpIcon text={ help } /> }
			</div>
		</div>
	);
};

// =====
// HOOKS
// =====

const useExperimentType = () =>
	useSelect( ( select ) => select( NAB_EDITOR ).getExperimentType(), [] );

const useDefaults = () => {
	const type = useExperimentType();
	return useSelect(
		( select ) =>
			select( NAB_EXPERIMENTS ).getExperimentType( type )?.defaults,
		[ type ]
	);
};

const useDuplicator = ( alternative: Maybe< Alternative > ) => {
	const alternatives = useSelect(
		( select ) => select( NAB_EDITOR ).getAlternatives(),
		[]
	);
	const { replaceAlternatives } = useDispatch( NAB_EDITOR );
	return alternative
		? () =>
				void replaceAlternatives(
					alternatives.map( ( a ) => a.id ),
					[ ...alternatives, duplicateAlternative( alternative ) ]
				)
		: () => void null;
};

const useAlternative = ( alternativeId: AlternativeId ) => {
	const [ didApplyDefaults, markApplyDefaults ] = useState( false );
	const defaults = useDefaults();
	const alternative = useSelect(
		( select ) => select( NAB_EDITOR ).getAlternative( alternativeId ),
		[ alternativeId ]
	);
	const { setAlternative } = useDispatch( NAB_EDITOR );

	useEffect( () => {
		if ( didApplyDefaults ) {
			return;
		}

		if ( ! defaults || ! alternative ) {
			return;
		}

		const defaultAttributes =
			'control' === alternative.id
				? defaults.original
				: defaults.alternative;

		void setAlternative( alternative.id, {
			...alternative,
			attributes: {
				...defaultAttributes,
				...alternative.attributes,
			},
		} );
		markApplyDefaults( true );
	}, [ defaults, alternative, didApplyDefaults, setAlternative ] );

	return didApplyDefaults ? alternative : undefined;
};

const useCanBeRemoved = () => {
	const [ status ] = useExperimentAttribute( 'status' );
	return useSelect(
		( select ) => {
			const { getAlternativeIds } = select( NAB_EDITOR );
			const isPaused = ( status || '' ).includes( 'paused' );
			return getAlternativeIds().length > 2 && ! isPaused;
		},
		[ status ]
	);
};

const useIsBeingSaved = () =>
	useSelect(
		( select ) => select( NAB_EDITOR ).isExperimentBeingSaved(),
		[]
	);

type View = ( props: ExperimentEditProps< Dict > ) => JSX.Element;
const EMPTY_VIEW: View = () => <></>;
const useView = ( index: number ): View =>
	useSelect(
		( select ) => {
			const { getExperimentView } = select( NAB_EXPERIMENTS );
			const { getExperimentType } = select( NAB_EDITOR );
			const view = ! index
				? getExperimentView( getExperimentType(), 'original' )
				: getExperimentView( getExperimentType(), 'alternative' );
			return view ?? EMPTY_VIEW;
		},
		[ index ]
	);

const useHelp = ( index: number ) =>
	useSelect(
		( select ) => {
			const { getExperimentType } = select( NAB_EDITOR );
			const { getHelpString } = select( NAB_EXPERIMENTS );

			if ( 0 === index ) {
				return getHelpString( getExperimentType(), 'original' );
			}

			if ( 1 === index ) {
				return getHelpString( getExperimentType(), 'alternative' );
			}

			return '';
		},
		[ index ]
	);

const useIsExternalEdit = () => {
	const type = useExperimentType();
	const control = useSelect(
		( select ) => select( NAB_EDITOR ).getAlternative( 'control' ),
		[]
	);
	return useSelect(
		( select ) => {
			const { getExperimentSupport } = select( NAB_EXPERIMENTS );
			const alternativeEdition = getExperimentSupport(
				type,
				'alternativeEdition'
			);
			if ( 'external' !== alternativeEdition?.type ) {
				return false;
			}
			if ( 'function' !== typeof alternativeEdition.enabled ) {
				return true;
			}
			return (
				!! control && alternativeEdition.enabled( control.attributes )
			);
		},
		[ type, control ]
	);
};

const useIsPreviewDisabled = (
	alternative: Maybe< Alternative >,
	control: Maybe< Alternative >
) =>
	useSelect(
		( select ): boolean => {
			const { getExperimentType } = select( NAB_EXPERIMENTS );
			const type = getExperimentType(
				select( NAB_EDITOR ).getExperimentType()
			);
			if ( ! type || ! alternative || ! control ) {
				return false;
			}
			return !! type.checks.isAlternativePreviewDisabled?.(
				alternative.attributes,
				control.attributes,
				select
			);
		},
		[ control, alternative ]
	);

const useCustomActions = (
	alternative: Maybe< Alternative >,
	control: Maybe< Alternative >
) =>
	useSelect(
		( select ): ReadonlyArray< AlternativeCustomAction > => {
			const { getExperimentType } = select( NAB_EXPERIMENTS );
			const type = getExperimentType(
				select( NAB_EDITOR ).getExperimentType()
			);
			if ( ! type || ! alternative || ! control ) {
				return [];
			}

			return type.supports.alternativeCustomActions ?? [];
		},
		[ control, alternative ]
	);
