/**
 * WordPress dependencies
 */
import { _x, sprintf } from '@safe-wordpress/i18n';

/**
 * External dependencies
 */
import { keys } from 'lodash';
import { formatNumber } from '@nab/i18n';
import { Line } from 'react-chartjs-2';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import 'chartjs-adapter-moment';
import {
	Chart as ChartJS,
	BarElement,
	CategoryScale,
	Legend,
	LineElement,
	LinearScale,
	PointElement,
	TimeScale,
	Title,
	Tooltip,
} from 'chart.js';
import { COLORS, aggregateResultsByTimeGrouping, getLetter } from '@nab/utils';

ChartJS.register(
	BarElement,
	CategoryScale,
	ChartDataLabels,
	Legend,
	LineElement,
	LinearScale,
	PointElement,
	TimeScale,
	Title,
	Tooltip
);

import type { ChartDataset, ScatterDataPoint } from 'chart.js';
import type { AlternativeTrackingData, GoalId, Maybe } from '@nab/types';

/**
 * Internal dependencies
 */
import { getEvolutionChartOptions } from './helpers';

export type ViewsAndConversionsTimelineProps = {
	readonly alternatives: Maybe< ReadonlyArray< AlternativeTrackingData > >;
	readonly firstDayOfWeek: number;
	readonly goal: GoalId;
	readonly unique: boolean;
	readonly timeGrouping: 'daily' | 'weekly' | 'monthly';
	readonly winner: Maybe< number >;
};

export const ViewsAndConversionsTimelineChart = ( {
	alternatives = [],
	firstDayOfWeek,
	goal,
	unique,
	winner,
	timeGrouping,
}: ViewsAndConversionsTimelineProps ): JSX.Element => {
	const colors = [
		COLORS.nabBackgroundAlternativeA,
		COLORS.nabBackgroundAlternativeB,
		COLORS.nabBackgroundAlternativeC,
		COLORS.nabBackgroundAlternativeD,
		COLORS.nabBackgroundAlternativeE,
		COLORS.nabBackgroundAlternativeF,
	];

	const pointStyles = [
		{ type: 'circle' as const, size: 2.5 },
		{ type: 'rect' as const, size: 3 },
		{ type: 'triangle' as const, size: 3.5 },
	];

	const lines = alternatives.map( ( a ) =>
		getConversions( a, goal, unique, timeGrouping, firstDayOfWeek )
	);
	const datasets: ChartDataset[] = [
		{
			label: _x( 'Views', 'text', 'nelio-ab-testing' ),
			backgroundColor: COLORS.wpAdminThemeColorRgb,
			borderColor: COLORS.wpAdminThemeColorRgb,
			fill: false,
			tension: 0.4,
			pointStyle: pointStyles[ 0 ]?.type,
			pointRadius: pointStyles[ 0 ]?.size ?? 3,
			pointHitRadius: 4,
			pointHoverRadius: 4,
			data: getViews(
				alternatives,
				unique,
				timeGrouping,
				firstDayOfWeek
			),
		},
		...lines.map( ( l, i ) => ( {
			label:
				i === 0
					? _x(
							'Conversions A (Control)',
							'text',
							'nelio-ab-testing'
					  )
					: sprintf(
							/* translators: %s: Variant letter. */
							_x( 'Conversions %s', 'text', 'nelio-ab-testing' ),
							getLetter( i )
					  ),
			backgroundColor: colors[ i % colors.length ],
			borderColor: colors[ i % colors.length ],
			fill: false,
			tension: 0.4,
			borderWidth: i === winner ? 3 : 2,
			pointStyle: pointStyles[ i % pointStyles.length ]?.type,
			pointRadius:
				( pointStyles[ i % pointStyles.length ]?.size ?? 3 ) *
				( i === winner ? 1.2 : 1 ),
			pointHitRadius: 4,
			pointHoverRadius: 4,
			data: l,
		} ) ),
	];

	const options = getEvolutionChartOptions( {
		timeGrouping,
		title: _x( 'Views and Conversions', 'text', 'nelio-ab-testing' ),
		tooltip: ( context ) =>
			sprintf(
				'%1$s: %2$s%%',
				context.dataset.label,
				formatNumber( context.raw.y || 0 )
			),
	} );

	// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
	return <Line data={ { datasets } as any } options={ options as any } />;
};

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

const getViews = (
	alternatives: ReadonlyArray< AlternativeTrackingData >,
	unique: boolean,
	timeGrouping: 'daily' | 'weekly' | 'monthly',
	firstDayOfWeek: number
): ScatterDataPoint[] => {
	const viewsPerDate = {} as Record< string, number >;
	alternatives.forEach( ( a ) =>
		aggregateResultsByTimeGrouping(
			a.timeline,
			timeGrouping,
			firstDayOfWeek
		).forEach( ( day ) => {
			const existing = viewsPerDate[ day.date ] || 0;
			const views = unique ? day.uniqueVisits : day.visits;
			viewsPerDate[ day.date ] = existing + views;
		} )
	);
	const dates = keys( viewsPerDate ).sort();
	return dates.map(
		( date ): ScatterDataPoint => ( {
			x: date as unknown as number,
			y: viewsPerDate[ date ] || 0,
		} )
	);
};

const getConversions = (
	alternative: Maybe< AlternativeTrackingData >,
	goal: GoalId,
	unique: boolean,
	timeGrouping: 'daily' | 'weekly' | 'monthly',
	firstDayOfWeek: number
): ScatterDataPoint[] => {
	if ( ! alternative ) {
		return [];
	}

	return aggregateResultsByTimeGrouping(
		alternative.timeline,
		timeGrouping,
		firstDayOfWeek
	).map(
		( day ): ScatterDataPoint => ( {
			x: day.date as unknown as number,
			y: unique
				? day.uniqueConversions[ goal ] || 0
				: day.conversions[ goal ] || 0,
		} )
	);
};
