/**
 * WordPress dependencies
 */
import { Button } from '@safe-wordpress/components';
import {
	useDispatch,
	useSelect,
	type SelectFunction,
} from '@safe-wordpress/data';
import { createInterpolateElement } from '@safe-wordpress/element';
import { _n, _x, sprintf } from '@safe-wordpress/i18n';

/**
 * External dependencies
 */
import { isFunction } from 'lodash';
import { HelpIcon } from '@nab/components';
import { store as NAB_DATA, usePageAttribute } from '@nab/data';
import { formatI18nDate } from '@nab/date';
import { store as NAB_EXPERIMENTS } from '@nab/experiments';
import { computeDuration, getLetter, hasHead } from '@nab/utils';
import type {
	Alternative,
	AlternativeId,
	AlternativeTrackingData,
	ECommercePlugin,
	Experiment,
	ExperimentId,
	GoalId,
	Maybe,
	Opportunity,
	PostId,
	Url,
} from '@nab/types';

/**
 * Internal dependencies
 */
import './style.scss';
import { useExperimentAttribute } from '../hooks';
import { getMoneyLabel } from '../utils';
import { TypicalSampleSize } from './typical-sample-size';

import type { ControlAttributes as PostControlAttributes } from '../../../../../../../packages/experiment-library/post/types';
import type { ControlAttributes as UrlControlAttributes } from '../../../../../../../packages/experiment-library/url/types';

type ExperimentStatus = 'running' | 'finished';

type Results =
	| { readonly type: 'loading' }
	| { readonly type: 'collecting-results' }
	| ( { readonly type: 'winner' } & DataDetails & Winner )
	| ( { readonly type: 'possible-winner' } & DataDetails & Winner )
	| ( { readonly type: 'no-winner' } & DataDetails );

type Winner = {
	readonly winner: {
		readonly id: AlternativeId;
		readonly name: string;
		readonly isLastApplied: boolean;
	};
};

type DataDetails = {
	readonly alternativeCount: number;
	readonly minConfidence: number;
	readonly confidence: number;
	readonly uplift: number;
	readonly estimatedImpact?: {
		readonly value: string;
		readonly uplift?: number;
	};
};

type SummaryContent = {
	readonly eyebrow: string;
	readonly title: string;
	readonly subtitle: string | JSX.Element;
	readonly description: string | JSX.Element;
	readonly icon: JSX.Element;
	readonly primaryAction?: {
		readonly label: string;
		readonly isBusy: boolean;
		readonly isDisabled: boolean;
		readonly onClick: () => void;
	};
	readonly secondaryAction?: {
		readonly label: string;
		readonly isBusy: boolean;
		readonly isDisabled: boolean;
		readonly onClick: () => void;
	};
};

export const Summary = (): JSX.Element => {
	const {
		id: experimentId,
		status: experimentStatus,
		startDate,
		endDate,
		hasValueEstimate,
	} = useExperiment();
	const results = useResults();

	const winner = getWinner( results );
	const [ _, doApplyWinner ] = usePageAttribute(
		'results/alternativeToApply',
		false
	);
	const applyWinner = useCanApplyAlternativeWinner( experimentId, winner )
		? () => doApplyWinner( winner )
		: undefined;

	const { stopExperiment: doStopTest } = useDispatch( NAB_DATA );
	const stopTest = useCanStopTest() ? doStopTest : undefined;
	const [ isStoppingTest ] = usePageAttribute(
		'editor/isExperimentBeingStopped',
		false
	);

	const opportunity = useOpportunity( experimentId );
	const { generateExperimentSuggestions } = useDispatch( NAB_DATA );
	const doCreateFollowUpTest = () =>
		opportunity && generateExperimentSuggestions( opportunity );
	const createFollowUpTest =
		useCanCreateFollowUpTest() && !! opportunity
			? doCreateFollowUpTest
			: undefined;
	const [ isCreatingFollowUpTest ] = usePageAttribute(
		'results/isCreatingFollowUpTest',
		false
	);

	const areActionsDisabled = isStoppingTest || isCreatingFollowUpTest;

	const args = {
		areActionsDisabled,
		isCreatingFollowUpTest,
		isStoppingTest,
		isWinnerLastApplied: getIsWinnerLastApplied( results ),
		results,
		status: experimentStatus,
		applyWinner,
		createFollowUpTest,
		stopTest,
	};
	const content =
		'running' === experimentStatus
			? getRunningSummaryContent( args )
			: getFinishedSummaryContent( args );

	return (
		<section
			className={ `nab-summary nab-summary--${ results.type }` }
			aria-label={
				! content
					? _x( 'Loading result summary', 'text', 'nelio-ab-testing' )
					: content.title
			}
			aria-busy={ ! content ? 'true' : undefined }
		>
			{ ! content ? (
				<ResultsSummarySkeleton hasValueEstimate={ hasValueEstimate } />
			) : (
				<>
					<div className="nab-summary__main">
						<div className="nab-summary__overview">
							<div
								className="nab-summary__icon"
								aria-hidden="true"
							>
								{ content.icon }
							</div>

							<div className="nab-summary__conclusion">
								<p className="nab-summary__eyebrow">
									{ content.eyebrow }
								</p>

								<h2
									id="nab-summary-title"
									className="nab-summary__title"
								>
									{ content.title }
								</h2>

								<p className="nab-summary__subtitle">
									{ content.subtitle }
								</p>

								<p className="nab-summary__description">
									{ content.description }
								</p>

								<TypicalSampleSize
									type={ results.type }
									confidence={
										'confidence' in results
											? results.confidence
											: undefined
									}
									minConfidence={
										'minConfidence' in results
											? results.minConfidence
											: undefined
									}
								/>
							</div>
						</div>

						<div className="nab-summary__metrics">
							<SummaryMetric
								label={ _x(
									'Confidence',
									'text',
									'nelio-ab-testing'
								) }
								help={ _x(
									'How strongly the available data supports the current result',
									'text',
									'nelio-ab-testing'
								) }
								value={ formatConfidence( results ) }
								detail={ getConfidenceLabel( results ) }
							/>

							{ hasEstimatedImpact( results ) && (
								<SummaryMetric
									label={ _x(
										'Estimated impact',
										'text',
										'nelio-ab-testing'
									) }
									help={ _x(
										'The estimated improvement associated with the leading variant',
										'text',
										'nelio-ab-testing'
									) }
									value={ formatEstimatedImpact( results ) }
									detail={ getImpactDetail( results ) }
								/>
							) }

							<ExperimentStatusMetric
								status={ experimentStatus }
								startDate={ startDate }
								endDate={ endDate }
							/>
						</div>
					</div>

					{ ( content.primaryAction || content.secondaryAction ) && (
						<div className="nab-summary__actions">
							{ content.secondaryAction && (
								<Button
									className="nab-summary__button nab-summary__button--secondary"
									variant="secondary"
									onClick={ content.secondaryAction.onClick }
								>
									{ content.secondaryAction.label }
								</Button>
							) }

							{ content.primaryAction && (
								<Button
									className="nab-summary__button nab-summary__button--primary"
									variant="primary"
									onClick={ content.primaryAction.onClick }
								>
									{ content.primaryAction.label }
								</Button>
							) }
						</div>
					) }
				</>
			) }
		</section>
	);
};

type GetSummaryContentArgs = {
	readonly areActionsDisabled: boolean;
	readonly isCreatingFollowUpTest: boolean;
	readonly isStoppingTest: boolean;
	readonly isWinnerLastApplied: boolean;
	readonly results: Results;
	readonly applyWinner?: () => void;
	readonly createFollowUpTest?: () => void;
	readonly stopTest?: () => void;
};

const getRunningSummaryContent = (
	args: GetSummaryContentArgs
): Maybe< SummaryContent > => {
	switch ( args.results.type ) {
		case 'loading':
			return undefined;

		case 'winner': {
			const alternativeName = args.results.winner.name;
			const alternativeId = args.results.winner.id;

			return {
				eyebrow: _x( 'Winner found', 'text', 'nelio-ab-testing' ),
				title: sprintf(
					/* translators: %s: Variant name (as in “Variant X”). */
					_x( '%s is the clear winner', 'text', 'nelio-ab-testing' ),
					alternativeName
				),
				subtitle: getUpliftHeadline( args.results, 'running' ),
				description: sprintf(
					'%1$s %2$s',
					// eslint-disable-next-line no-nested-ternary
					'control' === alternativeId
						? 2 === args.results.alternativeCount
							? _x(
									'The control is outperforming variant B.',
									'text',
									'nelio-ab-testing'
							  )
							: _x(
									'The control is outperforming the other variants.',
									'text',
									'nelio-ab-testing'
							  )
						: sprintf(
								/* translators: %s: Variant name (as in “Variant X”). */
								_x(
									'%s is outperforming the control.',
									'text',
									'nelio-ab-testing'
								),
								alternativeName
						  ),
					_x(
						'We recommend stopping the test and applying the winning variant to all visitors.',
						'text',
						'nelio-ab-testing'
					)
				),
				icon: <TrophyIcon />,
				primaryAction: args.stopTest
					? {
							isBusy: args.isStoppingTest,
							isDisabled:
								args.areActionsDisabled &&
								! args.isStoppingTest,
							label: _x(
								'Stop Test Now',
								'command',
								'nelio-ab-testing'
							),
							onClick: args.stopTest,
					  }
					: undefined,
			};
		}

		case 'possible-winner': {
			const alternativeName = args.results.winner.name;

			return {
				eyebrow: _x( 'Promising result', 'text', 'nelio-ab-testing' ),
				title: sprintf(
					/* translators: %s: Variant name. */
					_x( '%s is currently leading', 'text', 'nelio-ab-testing' ),
					alternativeName
				),
				subtitle: getUpliftHeadline( args.results, 'running' ),
				description: _x(
					'This variant is currently ahead, but more data is needed before declaring a winner.',
					'text',
					'nelio-ab-testing'
				),
				icon: <TrendIcon />,
			};
		}

		case 'no-winner':
			return {
				eyebrow: _x(
					'No significant difference',
					'text',
					'nelio-ab-testing'
				),
				title: _x( 'No clear winner yet', 'text', 'nelio-ab-testing' ),
				subtitle: _x(
					'No variant is clearly outperforming the control',
					'text',
					'nelio-ab-testing'
				),
				description: _x(
					'The test hasn’t identified a clear winner yet. Keep it running or try a different hypothesis.',
					'text',
					'nelio-ab-testing'
				),
				icon: <CollectingIcon />,
			};

		case 'collecting-results':
			return {
				eyebrow: _x( 'Collecting results', 'text', 'nelio-ab-testing' ),
				title: _x( 'Not enough data yet', 'text', 'nelio-ab-testing' ),
				subtitle: _x(
					'The test is still collecting data',
					'text',
					'nelio-ab-testing'
				),
				description: _x(
					'Keep the test running until there’s enough data to evaluate the variants reliably.',
					'text',
					'nelio-ab-testing'
				),
				icon: <CollectingIcon />,
			};
	}
};

const getFinishedSummaryContent = (
	args: {
		readonly results: Results;
	} & GetSummaryContentArgs
): Maybe< SummaryContent > => {
	switch ( args.results.type ) {
		case 'loading':
			return undefined;

		case 'winner': {
			const alternativeName = args.results.winner.name;
			const alternativeId = args.results.winner.id;

			return {
				eyebrow: _x( 'Winner found', 'text', 'nelio-ab-testing' ),
				title: sprintf(
					/* translators: %s: Variant name (as in “Variant X”). */
					_x( '%s won', 'text', 'nelio-ab-testing' ),
					alternativeName
				),
				subtitle: getUpliftHeadline( args.results, 'finished' ),
				description: sprintf(
					'%1$s %2$s',
					'control' === alternativeId
						? _x(
								'This test protected your site from a change that would have reduced performance. Keeping the control was the right decision!',
								'text',
								'nelio-ab-testing'
						  )
						: _x(
								'This test uncovered a better-performing version of your page. Your visitors will benefit from this improvement.',
								'text',
								'nelio-ab-testing'
						  ),
					! args.isWinnerLastApplied && 'control' !== alternativeId
						? _x(
								'We recommend applying the winning version to all visitors.',
								'text',
								'nelio-ab-testing'
						  )
						: ''
				),
				icon: <TrophyIcon />,
				primaryAction: args.applyWinner
					? {
							isBusy: false,
							isDisabled: args.areActionsDisabled,
							label: sprintf(
								/* translators: %s: Variant name (as in “Variant X”). */
								_x( 'Apply %s', 'command', 'nelio-ab-testing' ),
								alternativeName
							),
							onClick: args.applyWinner,
					  }
					: undefined,
				secondaryAction: args.createFollowUpTest
					? {
							isBusy: args.isCreatingFollowUpTest,
							isDisabled:
								args.areActionsDisabled &&
								! args.isCreatingFollowUpTest,
							label: _x(
								'Create Follow-Up Test',
								'command',
								'nelio-ab-testing'
							),
							onClick: args.createFollowUpTest,
					  }
					: undefined,
			};
		}

		case 'possible-winner': {
			const alternativeName = args.results.winner.name;

			return {
				eyebrow: _x( 'Promising result', 'text', 'nelio-ab-testing' ),
				title: sprintf(
					/* translators: %s: Variant name. */
					_x( '%s finished ahead', 'text', 'nelio-ab-testing' ),
					alternativeName
				),
				subtitle: getUpliftHeadline( args.results, 'finished' ),
				description: _x(
					'The result was promising, but there wasn’t enough evidence to declare a clear winner.',
					'text',
					'nelio-ab-testing'
				),
				icon: <TrendIcon />,
				primaryAction: args.createFollowUpTest
					? {
							isBusy: args.isCreatingFollowUpTest,
							isDisabled:
								args.areActionsDisabled &&
								! args.isCreatingFollowUpTest,
							label: _x(
								'Create Follow-Up Test',
								'command',
								'nelio-ab-testing'
							),
							onClick: args.createFollowUpTest,
					  }
					: undefined,
			};
		}

		case 'no-winner':
			return {
				eyebrow: _x(
					'No significant difference',
					'text',
					'nelio-ab-testing'
				),
				title: _x( 'No clear winner', 'text', 'nelio-ab-testing' ),
				subtitle: _x(
					'None of the variants clearly outperformed the control',
					'text',
					'nelio-ab-testing'
				),
				description: _x(
					'The test did not identify a clear improvement. Consider trying a different hypothesis.',
					'text',
					'nelio-ab-testing'
				),
				icon: <NoDifferenceIcon />,
				secondaryAction: args.createFollowUpTest
					? {
							isBusy: args.isCreatingFollowUpTest,
							isDisabled:
								args.areActionsDisabled &&
								! args.isCreatingFollowUpTest,
							label: _x(
								'Create Follow-Up Test',
								'command',
								'nelio-ab-testing'
							),
							onClick: args.createFollowUpTest,
					  }
					: undefined,
			};

		case 'collecting-results':
			// NOTE. This branch will never run, because this state only occurs while the test is running.
			// Therefore, there’s no need to do anything (other than returning something so that TS doesn’t complain).
			return getRunningSummaryContent( args );
	}
};

type SummaryMetricProps = {
	label: string;
	help: string;
	value: string;
	detail?: string;
};

const SummaryMetric = ( {
	label,
	help,
	value,
	detail,
}: SummaryMetricProps ) => (
	<div className="nab-summary__metric">
		<div className="nab-summary__metric-label">
			<span>{ label }</span>
			<HelpIcon text={ help } />
		</div>

		<strong className="nab-summary__metric-value">{ value }</strong>

		{ detail && (
			<span className="nab-summary__metric-detail">
				{ createInterpolateElement( detail.replaceAll( '\n', '<br>' ), {
					br: <br />,
				} ) }
			</span>
		) }
	</div>
);

type ExperimentStatusMetricProps = {
	status: ExperimentStatus;
	startDate: string | false;
	endDate: string | false;
};

const ExperimentStatusMetric = ( {
	status,
	startDate,
	endDate,
}: ExperimentStatusMetricProps ) => {
	const start = toDate( startDate );
	const end =
		status === 'finished' && endDate ? toDate( endDate ) : new Date();

	return (
		<div className="nab-summary__metric">
			<div className="nab-summary__metric-label">Status</div>

			<span>
				<span
					className={ `nab-summary__status nab-summary__status--${ status }` }
				>
					{ status === 'running'
						? _x(
								'Running',
								'text (experiment status)',
								'nelio-ab-testing'
						  )
						: _x(
								'Finished',
								'text (experiment status)',
								'nelio-ab-testing'
						  ) }
				</span>
			</span>

			<span className="nab-summary__metric-detail">
				{ status === 'running'
					? sprintf(
							/* translators: %s: Start date. */
							_x( 'Started %s', 'text', 'nelio-ab-testing' ),
							formatI18nDate( start )
					  )
					: sprintf(
							/* translators: %s: End date. */
							_x( 'Ended %s', 'text', 'nelio-ab-testing' ),
							formatI18nDate( end )
					  ) }
				<br />
				{ status === 'running'
					? sprintf(
							/* translators: %s: Day number. */
							_x( 'Day %s', 'text', 'nelio-ab-testing' ),
							getDurationInDays( start, end )
					  )
					: sprintf(
							/* translators: %s: Amount of time, like “2 months and 3 days.” */
							_x( 'Ran for %s', 'text', 'nelio-ab-testing' ),
							getBeautifulDuration( start, end )
					  ) }
			</span>
		</div>
	);
};

const ResultsSummarySkeleton = ( {
	hasValueEstimate,
}: {
	readonly hasValueEstimate: boolean;
} ) => (
	<section>
		<div className="nab-summary__main nab-summary__skeleton-main">
			<div className="nab-summary__overview nab-summary__skeleton-overview">
				<div className="nab-summary__skeleton nab-summary__skeleton-icon" />
				<div className="nab-summary__skeleton nab-summary__skeleton-content" />
			</div>
			<div className="nab-summary__metrics nab-summary__skeleton-metrics">
				<div className="nab-summary__skeleton nab-summary__skeleton-metric" />
				<div className="nab-summary__skeleton nab-summary__skeleton-metric" />
				{ hasValueEstimate && (
					<div className="nab-summary__skeleton nab-summary__skeleton-metric" />
				) }
			</div>
		</div>
	</section>
);

const formatConfidence = ( results: Results ): string => {
	if ( 'loading' === results.type || 'collecting-results' === results.type ) {
		return '—';
	}

	if ( 0 === results.confidence ) {
		return '—';
	}

	return `${ Math.floor( results.confidence ) }%`;
};

const getConfidenceLabel = ( results: Results ): Maybe< string > => {
	if ( 'loading' === results.type || 'collecting-results' === results.type ) {
		return undefined;
	}

	if ( results.confidence >= 95 ) {
		return _x( 'Very high', 'text', 'nelio-ab-testing' );
	}

	if ( results.confidence >= 80 ) {
		return _x( 'High', 'text', 'nelio-ab-testing' );
	}

	if ( results.confidence >= 50 ) {
		return _x( 'Low to medium', 'text', 'nelio-ab-testing' );
	}

	return _x( 'Low', 'text', 'nelio-ab-testing' );
};

const getUpliftHeadline = (
	results: Results,
	status: 'running' | 'finished'
): string | JSX.Element => {
	if ( 'loading' === results.type || 'collecting-results' === results.type ) {
		return '';
	}

	if ( 'winner' === results.type || 'possible-winner' === results.type ) {
		if ( 'control' === results.winner.id ) {
			return results.alternativeCount > 2
				? _x(
						'No variant improved on the control',
						'text',
						'nelio-ab-testing'
				  )
				: _x(
						'Variant B didn’t improve on the control',
						'text',
						'nelio-ab-testing'
				  );
		}
	}

	if ( 'no-winner' === results.type || ! results.uplift ) {
		return status === 'running'
			? _x( 'Performance is being evaluated', 'text', 'nelio-ab-testing' )
			: _x( 'No measurable improvement', 'text', 'nelio-ab-testing' );
	}

	return createInterpolateElement(
		sprintf(
			/* translators: %s: Percentage value. */
			_x( '%s uplift over the control', 'text', 'nelio-ab-testing' ),
			`<strong>${ results.uplift.toFixed( 1 ) }%</strong>`
		),
		{ strong: <strong /> }
	);
};

function hasEstimatedImpact( results: Results ): boolean {
	if ( 'winner' !== results.type && 'possible-winner' !== results.type ) {
		return false;
	}
	return !! results.estimatedImpact;
}

const formatEstimatedImpact = ( results: Results ): string => {
	if ( 'loading' === results.type || 'collecting-results' === results.type ) {
		return '—';
	}

	if ( results.estimatedImpact ) {
		return results.estimatedImpact.value;
	}

	if ( results.uplift !== null && Number.isFinite( results.uplift ) ) {
		return formatSignedPercentage( results.uplift );
	}

	return '—';
};

const getImpactDetail = ( results: Results ): Maybe< string > => {
	if ( 'loading' === results.type || 'collecting-results' === results.type ) {
		return undefined;
	}

	const parts: string[] = [];

	if ( results.estimatedImpact ) {
		const period = _x( 'month', 'text', 'nelio-ab-testing' );
		parts.push( `/${ period }` );
		if (
			results.estimatedImpact.uplift &&
			Number.isFinite( results.estimatedImpact.uplift )
		) {
			parts.push(
				`(${ formatSignedPercentage(
					results.estimatedImpact.uplift
				) })`
			);
		}
	}

	return parts.join( ' ' ) || undefined;
};

const formatSignedPercentage = ( value: number ): string =>
	`${ value > 0 ? '+' : '' }${ value.toFixed( 1 ) }%`;

const getDurationInDays = ( startDate: Date, endDate: Date ): number => {
	const millisecondsPerDay = 1000 * 60 * 60 * 24;

	return Math.max(
		1,
		Math.ceil(
			( endDate.getTime() - startDate.getTime() ) / millisecondsPerDay
		)
	);
};

const getBeautifulDuration = ( startDate: Date, endDate: Date ): string => {
	const duration = computeDuration(
		startDate.toISOString(),
		endDate.toISOString()
	);

	const snippets = [
		duration.years,
		duration.months,
		duration.days,
		duration.hours,
		duration.minutes,
	];

	const { years, months, days, hours, minutes } = duration;

	const stringifiedSnippets = [
		sprintf(
			/* translators: %d: Number of years. */
			_n( '%d year', '%d years', years, 'nelio-ab-testing' ),
			years
		),
		sprintf(
			/* translators: %d: Number of months. */
			_n( '%d month', '%d months', months, 'nelio-ab-testing' ),
			months
		),
		/* translators: %d: Number of days. */
		sprintf( _n( '%d day', '%d days', days, 'nelio-ab-testing' ), days ),
		sprintf(
			/* translators: %d: Number of hours. */
			_n( '%d hour', '%d hours', hours, 'nelio-ab-testing' ),
			hours
		),
		sprintf(
			/* translators: %d: Number of minutes. */
			_n( '%d minute', '%d minutes', minutes, 'nelio-ab-testing' ),
			minutes
		),
	];

	for ( let i = 0; i < snippets.length - 1; ++i ) {
		if ( snippets[ i ] && snippets[ i + 1 ] ) {
			return joinTimes(
				stringifiedSnippets[ i ],
				stringifiedSnippets[ i + 1 ]
			);
		}

		if ( snippets[ i ] ) {
			return stringifiedSnippets[ i ] || '';
		}
	}

	if ( snippets[ snippets.length - 1 ] ) {
		return stringifiedSnippets[ snippets.length - 1 ] || '';
	}

	return _x( 'less than a minute', 'text', 'nelio-ab-testing' );
};

function joinTimes( a: string | undefined, b: string | undefined ): string {
	if ( a && b ) {
		const and = _x( 'and', 'text', 'nelio-ab-testing' );
		return `${ a } ${ and } ${ b }`;
	}

	return a || b || '';
}
const toDate = ( value: string | false ): Date =>
	value ? new Date( value ) : new Date();

const TrophyIcon = () => (
	<svg viewBox="0 0 24 24" aria-hidden="true">
		<path
			d="M8 4h8v3.5a4 4 0 0 1-8 0V4Zm0 1H5v2a4 4 0 0 0 3.4 4M16 5h3v2a4 4 0 0 1-3.4 4M12 12v4m-3 3h6m-5-3h4"
			fill="none"
			stroke="currentColor"
			strokeWidth="1.8"
			strokeLinecap="round"
			strokeLinejoin="round"
		/>
	</svg>
);

const TrendIcon = () => (
	<svg viewBox="0 0 24 24" aria-hidden="true">
		<path
			d="m4 16 5-5 4 4 7-8m-5 0h5v5"
			fill="none"
			stroke="currentColor"
			strokeWidth="1.8"
			strokeLinecap="round"
			strokeLinejoin="round"
		/>
	</svg>
);

const NoDifferenceIcon = () => (
	<svg viewBox="0 0 24 24" aria-hidden="true">
		<path
			d="m6 6 12 12M18 6 6 18"
			fill="none"
			stroke="currentColor"
			strokeWidth="1.8"
			strokeLinecap="round"
		/>
	</svg>
);

const CollectingIcon = () => (
	<svg viewBox="0 0 24 24" aria-hidden="true">
		<path
			d="M5 18V9m7 9V5m7 13v-6"
			fill="none"
			stroke="currentColor"
			strokeWidth="1.8"
			strokeLinecap="round"
		/>
	</svg>
);

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

const useResults = () =>
	useSelect( ( select ): Results => {
		const activeExperiment = select( NAB_DATA ).getPageAttribute(
			'editor/activeExperiment'
		);
		const isLoading = ! select( NAB_DATA ).hasFinishedResolution(
			'getExperimentResults',
			[ activeExperiment ]
		);
		if ( ! activeExperiment || isLoading ) {
			return { type: 'loading' };
		}

		const minSampleSize =
			select( NAB_DATA ).getPluginSetting( 'minSampleSize' );
		const pageViews =
			select( NAB_DATA ).getPageViews( activeExperiment ) || 0;
		const experimentStatus = select( NAB_DATA ).getExperimentAttribute(
			activeExperiment,
			'status'
		);

		if ( 'running' === experimentStatus && pageViews < minSampleSize ) {
			return { type: 'collecting-results' };
		}

		const goals =
			select( NAB_DATA ).getExperimentAttribute(
				activeExperiment,
				'goals'
			) || [];
		const activeGoalId =
			select( NAB_DATA ).getPageAttribute( 'editor/activeGoal' ) ||
			( goals[ 0 ]?.id ?? '' );
		const winners =
			select( NAB_DATA ).getWinnersInExperiment( activeExperiment );
		const winner = winners?.[ activeGoalId ];

		const minConfidence =
			select( NAB_DATA ).getPluginSetting( 'minConfidence' );
		const confidence = winner?.confidence || 0;

		const alternatives =
			select( NAB_DATA ).getExperimentAttribute(
				activeExperiment,
				'alternatives'
			) || [];

		const isUnique =
			select( NAB_DATA ).areUniqueResultsVisible( activeExperiment );
		const resultsOfAlternatives =
			select( NAB_DATA ).getResultsOfAlternatives( activeExperiment );
		const durationInDays = getDurationInDays(
			toDate(
				select( NAB_DATA ).getExperimentAttribute(
					activeExperiment,
					'startDate'
				) || false
			),
			'finished' === experimentStatus
				? toDate(
						select( NAB_DATA ).getExperimentAttribute(
							activeExperiment,
							'endDate'
						) || false
				  )
				: new Date()
		);
		const ecommerce = select( NAB_DATA ).getECommercePlugin(
			activeExperiment,
			activeGoalId
		);
		const estimatedImpact = ecommerce
			? getEstimatedImpact(
					activeGoalId,
					alternatives,
					resultsOfAlternatives,
					isUnique,
					ecommerce,
					durationInDays
			  )
			: undefined;

		const details: DataDetails = {
			alternativeCount: alternatives.length,
			minConfidence,
			confidence,
			uplift: 0,
			estimatedImpact,
		};

		const winningAlternative = alternatives[ winner?.alternative ?? 0 ];
		if ( winner === undefined || ! winningAlternative ) {
			return {
				type: 'no-winner',
				...details,
			};
		}

		const winnerResults = resultsOfAlternatives?.[ winningAlternative.id ];
		const uplift = isUnique
			? winnerResults?.uniqueImprovementFactors[ activeGoalId ] || 0
			: winnerResults?.improvementFactors[ activeGoalId ] || 0;

		return {
			type: confidence < minConfidence ? 'possible-winner' : 'winner',
			...details,
			uplift,
			winner: {
				id: winningAlternative.id,
				name: sprintf(
					/* translators: %s: Variant letter. */
					_x( 'Variant %s', 'text', 'nelio-ab-testing' ),
					getLetter( winner.alternative )
				),
				isLastApplied: !! winningAlternative.isLastApplied,
			},
		};
	}, [] );

const useExperiment = () => ( {
	id: useExperimentAttribute( 'id' ),
	status:
		'running' === useExperimentAttribute( 'status' )
			? ( 'running' as const )
			: ( 'finished' as const ),
	startDate: useExperimentAttribute( 'startDate' ) || ( false as const ),
	endDate: useExperimentAttribute( 'endDate' ) || ( false as const ),
	hasValueEstimate: useHasValueEstimate(),
} );

const useHasValueEstimate = (): boolean =>
	useSelect( ( select ) => {
		const activeExperiment = select( NAB_DATA ).getPageAttribute(
			'editor/activeExperiment'
		);
		if ( ! activeExperiment ) {
			return false;
		}
		const goals =
			select( NAB_DATA ).getExperimentAttribute(
				activeExperiment,
				'goals'
			) || [];
		const activeGoalId =
			select( NAB_DATA ).getPageAttribute( 'editor/activeGoal' ) ||
			( goals[ 0 ]?.id ?? '' );
		return !! select( NAB_DATA ).getECommercePlugin(
			activeExperiment,
			activeGoalId
		);
	}, [] );

// =======
// HELPERS
// =======

function getEstimatedImpact(
	goalId: GoalId,
	alternatives: ReadonlyArray< Alternative >,
	resultsOfAlternatives: Maybe<
		Record< AlternativeId, AlternativeTrackingData >
	>,
	isUnique: boolean,
	ecommerce: ECommercePlugin,
	durationInDays: number
): DataDetails[ 'estimatedImpact' ] {
	if ( ! alternatives.length || ! resultsOfAlternatives ) {
		return undefined;
	}

	const ids = alternatives.map( ( a ) => a.id );
	const values = isUnique
		? ids.map(
				( id ) =>
					resultsOfAlternatives[ id ]?.uniqueValues[ goalId ] || 0
		  )
		: ids.map(
				( id ) => resultsOfAlternatives[ id ]?.values[ goalId ] || 0
		  );

	const [ controlValue = 0, ...alternativeValues ] = values;
	const bestAlternativeValue = alternativeValues.reduce(
		( r, x ) => ( x > r ? x : r ),
		0
	);
	if ( ! bestAlternativeValue ) {
		return undefined;
	}

	const impact = bestAlternativeValue - controlValue;
	if ( impact <= 0 ) {
		return undefined;
	}

	const months = durationInDays / 30;
	return {
		value: '+' + getMoneyLabel( Math.floor( impact / months ), ecommerce ),
		uplift: controlValue
			? ( 100 * bestAlternativeValue ) / controlValue
			: undefined,
	};
}

function getWinner( results: Results ): Maybe< AlternativeId > {
	if ( 'winner' !== results.type && 'possible-winner' !== results.type ) {
		return undefined;
	}
	return results.winner.id;
}

const useCanApplyAlternativeWinner = (
	experimentId?: ExperimentId,
	winner?: AlternativeId
): winner is AlternativeId =>
	useSelect(
		( select ): boolean => {
			const canUserApplyAlternatives = select( NAB_DATA ).hasCapability(
				'edit_nab_experiments'
			);
			if ( ! canUserApplyAlternatives ) {
				return false;
			}

			const isPublicView = select( NAB_DATA ).getPageAttribute(
				'results/isPublicView'
			);
			if ( isPublicView ) {
				return false;
			}

			const experiment = select( NAB_DATA ).getExperiment( experimentId );
			if ( ! experiment || ! winner || 'control' === winner ) {
				return false;
			}

			const alternative = experiment.alternatives.find(
				( a ) => a.id === winner
			);
			if ( ! alternative || alternative.isLastApplied ) {
				return false;
			}

			const { getExperimentSupport } = select( NAB_EXPERIMENTS );

			const alternativeApplication = getExperimentSupport(
				experiment.type,
				'alternativeApplication'
			);

			const supportsAlternativeApplication = isFunction(
				alternativeApplication
			)
				? alternativeApplication(
						experiment.alternatives[ 0 ]?.attributes
				  )
				: !! alternativeApplication;

			return (
				experiment.status === 'finished' &&
				!! supportsAlternativeApplication
			);
		},
		[ experimentId, winner ]
	);

const useCanCreateFollowUpTest = () =>
	useSelect(
		( select ): boolean =>
			select( NAB_DATA ).hasCapability( 'edit_nab_experiments' ) &&
			!! select( NAB_DATA ).getPluginSetting( 'aiSettings' ),
		[]
	);

const useCanStopTest = () =>
	useSelect(
		( select ) =>
			select( NAB_DATA ).hasCapability( 'stop_nab_experiments' ),
		[]
	);

const POST_OPPORTUNITY_TYPES = [
	'nab/page',
	'nab/post',
	'nab/custom-post-type',
];

const useOpportunity = ( experimentId: Maybe< ExperimentId > ) =>
	useSelect(
		( select ): Maybe< Opportunity > => {
			const experiment = select( NAB_DATA ).getExperiment( experimentId );
			if ( ! isOpportunityFeasible( experiment ) ) {
				return undefined;
			}

			const target = getOpportunityTarget( select, experiment );
			if ( ! target ) {
				return undefined;
			}

			return {
				type: 'recently-finished-experiment',
				target,
				score: 100,
				meta: {
					experiment: {
						type: experiment.type,
						id: experiment.id,
						name: experiment.name,
						description: experiment.description,
						alternatives: experiment.alternatives,
						goals: experiment.goals,
						segments: experiment.segments,
						testedAt: experiment.endDate,
					},
				},
			};
		},
		[ experimentId ]
	);

function isOpportunityFeasible(
	experiment: Maybe< Experiment >
): experiment is Experiment {
	if ( ! experiment ) {
		return false;
	}

	if ( POST_OPPORTUNITY_TYPES.includes( experiment.type ) ) {
		return true;
	}

	return !! getSingleUrlScope( experiment );
}

function getOpportunityTarget(
	select: SelectFunction,
	experiment: Experiment
): Maybe< Opportunity[ 'target' ] > {
	if ( POST_OPPORTUNITY_TYPES.includes( experiment.type ) ) {
		const attrs = experiment.alternatives[ 0 ]
			.attributes as PostControlAttributes;
		const post = select( NAB_DATA ).getEntityRecord(
			attrs.postType,
			attrs.postId
		);
		if ( ! post ) {
			return undefined;
		}

		return {
			type: 'post',
			postId: post.id as PostId,
			postType: post.type,
			title: post.title,
			url: post.link as Url,
		};
	}

	const url = getSingleUrlScope( experiment );
	if ( url ) {
		return {
			type: 'url',
			title: '',
			url,
		};
	}

	return undefined;
}

function getSingleUrlScope( experiment: Experiment ): Maybe< Url > {
	if ( 'nab/url' === experiment.type ) {
		return (
			experiment.alternatives[ 0 ].attributes as UrlControlAttributes
		 ).url as Url;
	}

	const scope = experiment.scope ?? [];
	if ( ! hasHead( scope ) || scope.length !== 1 ) {
		return undefined;
	}

	const rule = scope[ 0 ].attributes;
	return rule.type === 'exact' ? ( rule.value as Url ) : undefined;
}

function getIsWinnerLastApplied( results: Results ): boolean {
	if ( results.type !== 'winner' && results.type !== 'possible-winner' ) {
		return false;
	}
	return results.winner.isLastApplied;
}
