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

/**
 * External dependencies
 */
import { drop, map, range, take, trim, values, without } from 'lodash';
import { store as NAB_DATA, usePageAttribute } from '@nab/data';
import { downloadAsCsv, getLetter, isDefined } from '@nab/utils';
import type {
	Alternative,
	Day,
	DayResults,
	Dict,
	Experiment,
	ExperimentId,
	ExportSettings,
	Goal,
	Maybe,
	Results,
	Segment,
} from '@nab/types';

/**
 * Internal dependencies
 */
import './style.scss';

export const ExportModal = (): JSX.Element | null => {
	const exp = useActiveExperiment();
	const results = useExperimentResults( exp?.id );
	const [ isExporting ] = usePageAttribute( 'results/isExporting', false );

	const [ settings, setSettings ] = useSettings( exp, results );
	const options = useOptions( exp, settings );
	const handleDownload = useDownloadHandle( settings, exp, results );

	const [ isOpen, setOpen ] = usePageAttribute(
		'results/isExportModalOpen',
		false
	);
	const close = () => setOpen( false );

	if ( ! isOpen ) {
		return null;
	}

	if ( ! exp ) {
		return null;
	}

	const canExport =
		!! settings.segments.length &&
		!! settings.goals.length &&
		!! settings.alternatives.length;

	const [ firstLevel, secondLevel, thirdLevel ] = settings.order;
	const firstLevelHierarchy = options.organizationHierarchy;
	const isFirstLevelValid = firstLevelHierarchy.some(
		( o ) => o.value === firstLevel
	);
	const secondLevelHierarchy = firstLevelHierarchy.filter(
		( h ) => h.value !== firstLevel
	);
	const isSecondLevelValid = secondLevelHierarchy.some(
		( o ) => o.value === secondLevel
	);
	const thirdLevelHierarchy = secondLevelHierarchy.filter(
		( h ) => h.value !== secondLevel
	);

	return (
		<Modal
			className="nab-export-modal"
			title={ _x( 'Export Results', 'text', 'nelio-ab-testing' ) }
			isDismissible={ true }
			onRequestClose={ close }
			shouldCloseOnClickOutside={ false }
		>
			<p>
				{ options &&
					_x(
						'Customize your data export using the following options:',
						'user',
						'nelio-ab-testing'
					) }
			</p>

			<div className="nab-export-modal__section">
				<div className="nab-export-modal__section-name">
					{ _x( 'Alternatives', 'text', 'nelio-ab-testing' ) }
				</div>
				<div className="nab-export-modal__section-options nab-export-modal__section-options--checkboxes">
					{ options.alternatives.map( ( a, i ) => (
						<CheckboxControl
							key={ `alternative-${ i }` }
							checked={ settings.alternatives.includes( a.id ) }
							onChange={ ( c ) =>
								setSettings( {
									alternatives: toggle(
										map( options.alternatives, 'id' ),
										c,
										settings.alternatives,
										a.id
									),
								} )
							}
							label={ getAlternativeLabel( exp, a ) }
						/>
					) ) }
				</div>
			</div>

			{ 1 < options.goals.length && (
				<div className="nab-export-modal__section">
					<div className="nab-export-modal__section-name">
						{ _x( 'Goals', 'text', 'nelio-ab-testing' ) }
					</div>
					<div className="nab-export-modal__section-options nab-export-modal__section-options--checkboxes">
						{ exp.goals.map( ( goal, index ) => (
							<CheckboxControl
								key={ `alternative-${ index }` }
								checked={ settings.goals.includes( goal.id ) }
								onChange={ ( checked ) => {
									const goals = toggle(
										map( options.goals, 'id' ),
										checked,
										settings.goals,
										goal.id
									);
									setSettings( {
										goals,
										valueGoals: options.goals
											.filter( isValueGoal )
											.filter( ( g ) =>
												goals.includes( g.id )
											)
											.map( ( g ) => g.id ),
									} );
								} }
								label={ getGoalLabel( exp, goal ) }
							/>
						) ) }
					</div>
				</div>
			) }

			{ 1 < options.segments.length && (
				<div className="nab-export-modal__section">
					<div className="nab-export-modal__section-name">
						{ _x( 'Segments', 'text', 'nelio-ab-testing' ) }
					</div>
					<div className="nab-export-modal__section-options nab-export-modal__section-options--checkboxes">
						{ [ { id: 'default' as const }, ...exp.segments ].map(
							( s, i ) => (
								<CheckboxControl
									key={ `alternative-${ i }` }
									checked={ settings.segments.includes(
										s.id
									) }
									onChange={ ( c ) =>
										setSettings( {
											segments: toggle(
												map( options.segments, 'id' ),
												c,
												settings.segments,
												s.id
											),
										} )
									}
									label={ getSegmentLabel( exp, s ) }
								/>
							)
						) }
					</div>
				</div>
			) }

			{ 2 <= firstLevelHierarchy.length && (
				<div className="nab-export-modal__section">
					<div className="nab-export-modal__section-name">
						{ _x( 'Organization', 'text', 'nelio-ab-testing' ) }
					</div>
					<div className="nab-export-modal__section-options nab-export-modal__section-options--selects">
						{ isFirstLevelValid && (
							<SelectControl
								options={ firstLevelHierarchy }
								onChange={ ( value ) =>
									setSettings( {
										order: insertAt(
											0,
											settings.order,
											value
										),
									} )
								}
								value={ firstLevel }
								label={ _x(
									'Group results by:',
									'command',
									'nelio-ab-testing'
								) }
							/>
						) }
						{ isSecondLevelValid && (
							<SelectControl
								options={ secondLevelHierarchy }
								onChange={ ( value ) =>
									setSettings( {
										order: insertAt(
											1,
											settings.order,
											value
										),
									} )
								}
								value={ secondLevel }
								label={
									isFirstLevelValid
										? _x(
												'Then by:',
												'text (then [group results] by)',
												'nelio-ab-testing'
										  )
										: _x(
												'Group results by:',
												'text',
												'nelio-ab-testing'
										  )
								}
							/>
						) }
						{ 1 === thirdLevelHierarchy.length && (
							<SelectControl
								options={ thirdLevelHierarchy }
								onChange={ ( value ) =>
									setSettings( {
										order: insertAt(
											2,
											settings.order,
											value
										),
									} )
								}
								value={ thirdLevel }
								label={ _x(
									'Then by:',
									'text (then [group results] by)',
									'nelio-ab-testing'
								) }
							/>
						) }
					</div>
				</div>
			) }

			{ options.hasUniqueResults && (
				<CheckboxControl
					checked={ settings.uniqueResults }
					onChange={ ( c ) => setSettings( { uniqueResults: c } ) }
					label={ _x( 'Unique Results', 'text', 'nelio-ab-testing' ) }
				/>
			) }

			<div className="nab-export-modal__actions">
				<Button
					className="nab-export-modal__export-button"
					variant="secondary"
					onClick={ handleDownload }
					disabled={ ! canExport || isExporting }
					isBusy={ isExporting }
				>
					{ isExporting
						? _x( 'Downloading…', 'text', 'nelio-ab-testing' )
						: _x( 'Download CSV', 'command', 'nelio-ab-testing' ) }
				</Button>
			</div>
		</Modal>
	);
};

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

const useExperimentResults = ( id: Maybe< ExperimentId > ) =>
	useSelect(
		( select ) => select( NAB_DATA ).getExperimentResults( id ),
		[ id ]
	);

const useActiveExperiment = () =>
	useSelect( ( select ) => {
		const activeExperimentId = select( NAB_DATA ).getPageAttribute(
			'editor/activeExperiment'
		);
		return select( NAB_DATA ).getExperiment( activeExperimentId );
	}, [] );

function useOptions( exp: Maybe< Experiment >, settings: ExportSettings ) {
	const hasUniqueResults = useSelect(
		( select ) => select( NAB_DATA ).areUniqueResultsAvailable( exp?.id ),
		[ exp?.id ]
	);

	const segments =
		!! exp && exp.segments.length > 1
			? [ { id: 'default' as const }, ...exp.segments ]
			: [ exp?.segments[ 0 ] ?? { id: 'default' as const } ];
	const goals = exp?.goals ?? [];
	const alternatives = exp?.alternatives ?? [];

	type Option = {
		readonly value: ExportSettings[ 'order' ][ number ];
		readonly label: string;
	};
	const organizationHierarchy = [
		1 < settings.segments.length && {
			value: 'segments',
			label: _x( 'Segment', 'text (groups by)', 'nelio-ab-testing' ),
		},
		1 < settings.goals.length && {
			value: 'goals',
			label: _x( 'Goal', 'text (group by)', 'nelio-ab-testing' ),
		},
		1 < settings.alternatives.length && {
			value: 'alternatives',
			label: _x( 'Variant', 'text (group by)', 'nelio-ab-testing' ),
		},
	].filter( ( x ): x is Option => !! x );

	return {
		hasUniqueResults,
		segments,
		goals,
		alternatives,
		organizationHierarchy,
	};
}

const EMPTY_EXPORT_SETTINGS: ExportSettings = {
	alternatives: [],
	goals: [],
	valueGoals: [],
	order: [ 'segments', 'goals', 'alternatives' ],
	segments: [ 'default' ],
	uniqueResults: false,
};
function useSettings(
	exp: Maybe< Experiment >,
	results: Maybe< Results >
): [ ExportSettings, ( settings: Partial< ExportSettings > ) => void ] {
	const hasUniqueResults = useSelect(
		( select ) => select( NAB_DATA ).areUniqueResultsAvailable( exp?.id ),
		[ exp?.id ]
	);
	const [ uniqueResults ] = usePageAttribute(
		'results/shouldShowUniqueResults',
		false
	);
	const [ settings, setSettings ] = usePageAttribute(
		'results/exportSettings',
		EMPTY_EXPORT_SETTINGS
	);

	useEffect( () => {
		if ( exp && results && settings === EMPTY_EXPORT_SETTINGS ) {
			setSettings( {
				alternatives: exp.alternatives.map( ( a ) => a.id ),
				goals: exp.goals.map( ( g ) => g.id ),
				valueGoals: exp.goals
					.filter( isValueGoal )
					.map( ( g ) => g.id ),
				segments: [
					exp.segments.length !== 1
						? ( 'default' as const )
						: undefined,
					...exp.segments.map( ( s ) => s.id ),
				].filter( isDefined ),
				order: [ 'segments', 'goals', 'alternatives' ],
				uniqueResults: uniqueResults && hasUniqueResults,
			} );
		}
	}, [
		exp,
		results,
		hasUniqueResults,
		settings,
		uniqueResults,
		setSettings,
	] );

	return [
		settings,
		exp && results
			? ( s ) => setSettings( { ...settings, ...s } )
			: ( _ ) => void null,
	];
}

function useDownloadHandle(
	settings: ExportSettings,
	exp: Maybe< Experiment >,
	results: Maybe< Results >
): () => void {
	const [ _, markAsExporting ] = usePageAttribute(
		'results/isExporting',
		false
	);

	if ( ! exp || ! results ) {
		return () => void null;
	}

	return () => {
		markAsExporting( true );

		const flatResults = flattenResults( settings, exp, results );
		const table = tabulate( settings, exp, flatResults );
		const filename = sprintf(
			/* translators: %s: Unique identifier. */
			_x( 'results-export-%s.csv', 'text', 'nelio-ab-testing' ),
			exp?.id ?? 0
		);

		downloadAsCsv( table, filename );
		markAsExporting( false );
	};
}

type FlatResults = Array< { day: string } & Record< string, string | number > >;
function flattenResults(
	settings: ExportSettings,
	exp: Experiment,
	results: Results
): FlatResults {
	const data: Record< Day, FlatResults[ number ] > = {};

	const getData = ( seg: string, alt: string, day: DayResults ) => ( {
		day: day.date,
		[ `s:${ seg }|a:${ alt }|v` ]: settings.uniqueResults
			? day.uniqueVisits
			: day.visits,
		...( exp?.goals ?? [] ).reduce(
			( acc, goal ) => ( {
				...acc,
				[ `s:${ seg }|a:${ alt }|c:${ goal.id }` ]:
					settings.uniqueResults
						? day.uniqueConversions[ goal.id ]
						: day.conversions[ goal.id ],
				[ `s:${ seg }|a:${ alt }|val:${ goal.id }` ]:
					settings.uniqueResults
						? day.uniqueValues[ goal.id ]
						: day.values[ goal.id ],
			} ),
			{}
		),
	} );

	for ( const alt of exp?.alternatives ?? [] ) {
		const altResult = results?.alternatives[ alt.id ];
		for ( const day of altResult?.timeline ?? [] ) {
			data[ day.date ] = {
				...( data[ day.date ] ?? {} ),
				...getData( 'default', alt.id, day ),
			};
		}
	}

	for ( const segment of exp?.segments ?? [] ) {
		const segmentResult = results?.segments[ segment.id ];
		for ( const alt of exp?.alternatives ?? [] ) {
			const altResult = segmentResult?.alternatives[ alt.id ];
			for ( const day of altResult?.timeline ?? [] ) {
				data[ day.date ] = {
					...( data[ day.date ] ?? {} ),
					...getData( segment.id, alt.id, day ),
				};
			}
		}
	}

	return values( data );
}

function tabulate(
	settings: ExportSettings,
	exp: Experiment,
	results: FlatResults
): ReadonlyArray< ReadonlyArray< string | number > > {
	const zeroes = () => initDataColumns( settings, 0 );

	type Headers = Array< ReadonlyArray< string > >;
	const headers: Headers = getHeaders( settings, exp );

	type FullDataRow = [ string, ...Array< number > ];
	const dataRows: Array< FullDataRow > = [];

	type DataRow = Array< number >;
	const total: DataRow = zeroes();

	const visitPattern = getVisitPattern( settings );
	const visitIds1 = getVisitIds( settings, 0 );
	const visitIds2 = getVisitIds( settings, 1 );

	const conversionPattern = getConversionPattern( settings );
	const conversionIds1 = getConversionIds( settings, 0 );
	const conversionIds2 = getConversionIds( settings, 1 );
	const conversionIds3 = getConversionIds( settings, 2 );

	const valuePattern = getValuePattern( settings );
	const conversionValueIds1 = getConversionValueIds( settings, 0 );
	const conversionValueIds2 = getConversionValueIds( settings, 1 );
	const conversionValueIds3 = getConversionValueIds( settings, 2 );

	for ( const row of results ) {
		const dataRow: DataRow = zeroes();
		let index = 0;
		for ( const id1 of visitIds1 ) {
			for ( const id2 of visitIds2 ) {
				const key = sprintf( visitPattern, id1, id2 );
				const value = row[ key ];
				dataRow[ index ] = 'number' === typeof value ? value : 0;
				++index;
			}
		}

		for ( const id1 of conversionIds1 ) {
			for ( const id2 of conversionIds2 ) {
				for ( const id3 of conversionIds3 ) {
					const key = sprintf( conversionPattern, id1, id2, id3 );
					const value = row[ key ];
					dataRow[ index ] = 'number' === typeof value ? value : 0;
					++index;
				}
			}
		}

		for ( const id1 of conversionValueIds1 ) {
			for ( const id2 of conversionValueIds2 ) {
				for ( const id3 of conversionValueIds3 ) {
					const key = sprintf( valuePattern, id1, id2, id3 );
					const value = row[ key ];
					dataRow[ index ] = 'number' === typeof value ? value : 0;
					++index;
				}
			}
		}

		dataRows.push( [ row.day, ...dataRow ] );

		for ( let i = 0; i < total.length; ++i ) {
			total[ i ] = ( total[ i ] ?? 0 ) + ( dataRow[ i ] ?? 0 );
		}
	}

	return [
		...headers,
		...dataRows,
		[ _x( 'Total', 'text', 'nelio-ab-testing' ), ...total ],
	];
}

function getHeaders(
	settings: ExportSettings,
	exp: Experiment
): Array< ReadonlyArray< string > > {
	// Helper objects and functions.
	const titles: Record<
		ExportSettings[ 'order' ][ number ] | 'valueGoals',
		string[]
	> = {
		segments: settings.segments
			.map( ( id ) =>
				'default' === id
					? { id: 'default' as const }
					: exp.segments.find( ( s ) => s.id === id )
			)
			.map( ( s ) => ( s ? getSegmentLabel( exp, s ) : '' ) ),
		goals: settings.goals
			.map( ( id ) => exp.goals.find( ( g ) => g.id === id ) )
			.map( ( g ) => ( g ? getGoalLabel( exp, g ) : '' ) ),
		valueGoals: settings.valueGoals
			.map( ( id ) => exp.goals.find( ( g ) => g.id === id ) )
			.map( ( g ) => ( g ? getGoalLabel( exp, g ) : '' ) ),
		alternatives: settings.alternatives
			.map( ( id ) => exp.alternatives.find( ( a ) => a.id === id ) )
			.map( ( a ) => ( a ? getAlternativeLabel( exp, a ) : '' ) ),
	};

	const getTitles = (
		mode: 'visits' | 'conversions' | 'conversionValues',
		level: number
	) => {
		const dimension = settings.order[ level ];
		if ( ! dimension ) {
			return [ '' ];
		}
		if ( 'goals' === dimension && 'visits' === mode ) {
			return [ '' ];
		}
		if ( 'goals' === dimension && 'conversionValues' === mode ) {
			return titles.valueGoals;
		}
		return titles[ dimension ];
	};

	// ==================================================
	// ==================================================
	const headers: Array< ReadonlyArray< string > > = [];

	// ==================================================
	// ==================================================
	const firstRow = initDataColumns( settings, '' );
	let title = sprintf(
		/* translators: %1$d: Experiment id. %2$s: Experiment name. */
		_x( 'Results of Test %1$d – %2$s', 'text', 'nelio-ab-testing' ),
		exp.id,
		exp.name
	);
	if (
		settings.segments.length === 1 &&
		( exp.segments.length > 1 || settings.segments[ 0 ] !== 'default' )
	) {
		title += ', ' + titles.segments[ 0 ];
	}
	if ( settings.goals.length === 1 ) {
		title += ', ' + titles.goals[ 0 ];
	}
	if ( settings.alternatives.length === 1 ) {
		title += ', ' + titles.alternatives[ 0 ];
	}
	headers.push( [ title, ...firstRow ] );

	// ==================================================
	// ==================================================
	const secondRow = initDataColumns( settings, '' );
	secondRow[ 0 ] = _x( 'Page Views', 'text', 'nelio-ab-testing' );

	const numOfVisits = settings.segments.length * settings.alternatives.length;
	secondRow[ numOfVisits ] = _x( 'Conversions', 'text', 'nelio-ab-testing' );

	if ( settings.valueGoals.length ) {
		const numOfConversions =
			settings.segments.length *
			settings.goals.length *
			settings.alternatives.length;
		secondRow[ numOfVisits + numOfConversions ] = _x(
			'Conversion Values',
			'text',
			'nelio-ab-testing'
		);
	}

	headers.push( [ _x( 'Day', 'text', 'nelio-ab-testing' ), ...secondRow ] );

	for ( const index of [ 0, 1, 2 ] ) {
		if ( getTitles( 'conversions', index ).length <= 1 ) {
			continue;
		}

		const visitBlockCount =
			getTitles( 'visits', index - 1 ).length *
			getTitles( 'visits', index - 2 ).length;
		const visitItemCount =
			getTitles( 'visits', index + 1 ).length *
			getTitles( 'visits', index + 2 ).length;
		const visitTitles = repeat(
			visitBlockCount,
			getTitles( 'visits', index ).flatMap( ( t ) =>
				expand( visitItemCount, '', t )
			)
		).flatMap( ( x ) => x );

		const conversionBlockCount =
			getTitles( 'conversions', index - 1 ).length *
			getTitles( 'conversions', index - 2 ).length;
		const conversionItemCount =
			getTitles( 'conversions', index + 1 ).length *
			getTitles( 'conversions', index + 2 ).length;
		const conversionTitles = repeat(
			conversionBlockCount,
			getTitles( 'conversions', index ).flatMap( ( t ) =>
				expand( conversionItemCount, '', t )
			)
		).flatMap( ( x ) => x );

		const valueBlockCount =
			getTitles( 'conversionValues', index - 1 ).length *
			getTitles( 'conversionValues', index - 2 ).length;
		const valueItemCount =
			getTitles( 'conversionValues', index + 1 ).length *
			getTitles( 'conversionValues', index + 2 ).length;
		const valueTitles = repeat(
			valueBlockCount,
			getTitles( 'conversionValues', index ).flatMap( ( t ) =>
				expand( valueItemCount, '', t )
			)
		).flatMap( ( x ) => x );

		headers.push( [
			'',
			...visitTitles,
			...conversionTitles,
			...valueTitles,
		] );
	}

	return headers;
}

function getAlternativeLabel(
	exp: Experiment,
	alternative: Alternative< Dict >
) {
	const index = exp.alternatives.indexOf( alternative );
	const defaultLabel = sprintf(
		/* translators: %s: Variant letter. */
		_x( 'Variant %s', 'text', 'nelio-ab-testing' ),
		getLetter( index )
	);
	const name =
		0 === index
			? _x( 'Control Version', 'text', 'nelio-ab-testing' )
			: trim( alternative.attributes.name );
	return name ? sprintf( '%1$s – %2$s', defaultLabel, name ) : defaultLabel;
}

function getGoalLabel( exp: Experiment, goal: Goal ) {
	const index = exp.goals.indexOf( goal );
	const defaultLabel = sprintf(
		/* translators: %s: Goal index. */
		_x( 'Goal %s', 'text', 'nelio-ab-testing' ),
		index + 1
	);
	const name = trim( goal.attributes.name );
	return name ? sprintf( '%1$s – %2$s', defaultLabel, name ) : defaultLabel;
}

function getSegmentLabel(
	exp: Experiment,
	segment: { id: 'default' } | Segment
) {
	if ( isDefaultSegment( segment ) ) {
		return _x( 'Combined Segment (Auto)', 'text', 'nelio-ab-testing' );
	}

	const index = exp.segments.indexOf( segment );
	const defaultLabel = sprintf(
		/* translators: %s: Segment index. */
		_x( 'Segment %s', 'text', 'nelio-ab-testing' ),
		index + 1
	);
	const name = trim( segment.attributes.name );
	return name ? sprintf( '%1$s – %2$s', defaultLabel, name ) : defaultLabel;
}

const isDefaultSegment = (
	s: { id: 'default' } | Segment
): s is { id: 'default' } => s.id === 'default';

function toggle< T >(
	allSorted: ReadonlyArray< T >,
	include: boolean,
	items: ReadonlyArray< NoInfer< T > >,
	item: NoInfer< T >
): ReadonlyArray< T > {
	items = include ? [ ...items, item ] : without( items, item );
	return allSorted.filter( ( i ) => items.includes( i ) );
}

function insertAt< T >(
	position: number,
	items: ReadonlyArray< T >,
	item: T
): ReadonlyArray< T > {
	const clean = without( items, item );
	return [ ...take( clean, position ), item, ...drop( clean, position ) ];
}

const getVisitPattern = ( settings: ExportSettings ) => {
	const order = [ '', ...without( settings.order, 'goals' ) ];
	return 's:${s}|a:${a}|v'
		.replace( '${s}', `%${ order.indexOf( 'segments' ) }$s` )
		.replace( '${a}', `%${ order.indexOf( 'alternatives' ) }$s` );
};

const getVisitIds = (
	settings: ExportSettings,
	i: number
): ReadonlyArray< string > => {
	const dim = without( settings.order, 'goals' )[ i ] ?? 'alternatives';
	switch ( dim ) {
		case 'segments':
			return settings.segments;
		case 'goals':
			// We’ve excluded “goals” from the set of possible values in “dim”.
			return [];
		case 'alternatives':
			return settings.alternatives;
	}
};

const getConversionPattern = ( settings: ExportSettings ) => {
	const order = [ '', ...settings.order ];
	return 's:${s}|a:${a}|c:${g}'
		.replace( '${s}', '' + `%${ order.indexOf( 'segments' ) }$s` )
		.replace( '${a}', '' + `%${ order.indexOf( 'alternatives' ) }$s` )
		.replace( '${g}', '' + `%${ order.indexOf( 'goals' ) }$s` );
};

const getValuePattern = ( settings: ExportSettings ) =>
	getConversionPattern( settings ).replace( 'c:', 'val:' );

const getConversionIds = (
	settings: ExportSettings,
	i: number
): ReadonlyArray< string > => {
	const dim = settings.order[ i ] ?? 'alternatives';
	switch ( dim ) {
		case 'segments':
			return settings.segments;
		case 'goals':
			return settings.goals;
		case 'alternatives':
			return settings.alternatives;
	}
};

const getConversionValueIds = (
	settings: ExportSettings,
	i: number
): ReadonlyArray< string > => {
	const dim = settings.order[ i ] ?? 'alternatives';
	switch ( dim ) {
		case 'segments':
			return settings.segments;
		case 'goals':
			return settings.valueGoals;
		case 'alternatives':
			return settings.alternatives;
	}
};

function initDataColumns< T >( settings: ExportSettings, initialValue: T ) {
	const numOfVisits = settings.segments.length * settings.alternatives.length;
	const numOfConversions =
		settings.segments.length *
		settings.goals.length *
		settings.alternatives.length;
	const numOfConversionValues =
		settings.segments.length *
		settings.valueGoals.length *
		settings.alternatives.length;
	const numOfColumns =
		1 + numOfVisits + numOfConversions + numOfConversionValues;
	return range( numOfColumns - 1 ).map( () => initialValue );
}

function expand< T >( times: number, value: T, firstValue: T ): Array< T > {
	const result = range( times ).map( () => value );
	result[ 0 ] = firstValue;
	return result;
}

function repeat< T >( times: number, value: T ): Array< T > {
	return range( times ).map( () => value );
}

const isValueGoal = ( g: Maybe< Goal > ): boolean =>
	!! g?.attributes.useOrderRevenue;
