import React, { useEffect, useState } from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { api } from '../api';
import { Card, LineChart, Delta, BandScale, Composition, Button } from './ui';

/**
 * The three histories the dashboard opens with.
 *
 * Same instrument as the audit's "Findings over time", three times: a
 * full-width card, the change since the first point in the header, the line,
 * and the figures behind the last point underneath. Three subjects, three
 * colours, so a glance tells them apart — the score that should rise, the
 * findings that should fall, and the coverage that should keep pace.
 *
 * Every series is read from a record the plugin already keeps, not from a
 * sampler that starts empty: the score is stamped on each recalculation, the
 * findings on each audit run, and the day a page was analysed lives inside its
 * stored analysis. A site that has been running a fortnight sees three real
 * lines the first time this loads.
 */

/** One card. Identical anatomy to the audit's, only the subject changes. */
function TrendCard({ title, subtitle, series, color, label, formatValue, invert = false, tiles = [], breakdown = null, bands = null, note = null, status = null, statusAction = null }) {
	const first = series.length > 1 ? series[0].value : null;
	const last = series.length ? series[series.length - 1].value : null;
	const delta = first === null ? null : last - first;
	// A line is worth drawing when it has three points and they are not all
	// the same number.
	const hasShape = series.length >= 3 && new Set(series.map((p) => p.value)).size > 1;

	return (
		<Card
			title={title}
			subtitle={subtitle}
			action={
				delta === null ? null : (
					<Delta value={delta} invert={invert} suffix={` ${__('since first reading', 'ai-rank-suite')}`} />
				)
			}
		>
			{/*
			  * A line, or the thing the number is made of.
			  *
			  * Two readings of the same value draw a perfectly flat bar across
			  * the card: it looks like a measurement and it is the absence of
			  * one. But removing the chart until history exists is worse — the
			  * card is then empty on exactly the day somebody is deciding
			  * whether this plugin is worth keeping.
			  *
			  * So when there is no shape to show yet, the same space shows
			  * composition instead of history: where the number stands against
			  * the range it lives in, or what it is made of. Both are real
			  * readings of data that already exists, and both stop being drawn
			  * the moment an actual trend has something to say.
			  */}
			{hasShape ? (
				<LineChart data={series} label={label} color={color} height={140} formatValue={formatValue} bleed />
			) : (
				/*
				  * Only one of the two substitutes fits any given card. A
				  * band scale reads a number out of 100; "open findings" and
				  * "pages scored" are neither, so those cards carry the note
				  * alone rather than a scale that would invent a ceiling.
				  */
				breakdown
					? <Composition parts={breakdown} total={last} />
					: (Array.isArray(bands) && bands.length ? <BandScale value={last} color={color} bands={bands} /> : null)
			)}
			{!hasShape && (
				<p className="ars-trend-note">
					{series.length <= 1
						? __('The line starts once there are three readings — then its shape means something.', 'ai-rank-suite')
						: sprintf(
							/* translators: %s: number of readings so far. */
							__('%s readings so far, and nothing has moved. The line replaces this the moment it does.', 'ai-rank-suite'),
							series.length.toLocaleString()
						)}
				</p>
			)}

			{/*
			  * What one point is, and what the figure in the header compares
			  * against. "+6 since first reading" is unreadable without knowing
			  * whether the first reading is from yesterday or from March, and
			  * every one of these three windows is a different length.
			  */}
			{note && <p className="ars-trend-note">{note}</p>}

			{/*
			  * When this line last moved and when it moves next. A last label
			  * three weeks old looks like a fault; with the next date beside
			  * it, it is a schedule.
			  */}
			{status && (
				<div className="ars-trend-status flex flex-wrap items-center gap-3">
					<p className="ars-trend-note m-0">{status}</p>
					{statusAction}
				</div>
			)}

			{tiles.length > 0 && (
				<div className={`grid gap-3 mt-3 ${tiles.length === 3 ? 'sm:grid-cols-3' : 'sm:grid-cols-2'}`}>
					{tiles.map((t) => (
						<div key={t.label} className="ars-r-md px-3 py-2.5" style={{ background: 'var(--ars-surface-2)' }}>
							<div className="flex items-center justify-between gap-2">
								<span className="text-[12px] font-semibold" style={{ color: t.color }}>
									{t.label}
								</span>
								{t.delta !== null && t.delta !== undefined && <Delta value={t.delta} invert={!!t.invert} />}
							</div>
							<div className="t-num text-[18px] font-bold mt-0.5">
								{t.text ?? (t.value === null || t.value === undefined ? '–' : Number(t.value).toLocaleString())}
							</div>
						</div>
					))}
				</div>
			)}
		</Card>
	);
}

export default function DashboardTrends({ charts = ['score', 'findings', 'scored'], revision = 0 }) {
	const [data, setData] = useState(null);
	const [starting, setStarting] = useState(false);

	useEffect(() => {
		api.get('/dashboard/trends').then(setData).catch(() => setData({ score: [], findings: [], scored: [] }));
	}, [revision]);

	if (!data) return null;

	const score = data.score || [];
	const findings = data.findings || [];
	const scored = data.scored || [];
	const coverage = data.coverage || { published: 0, analyzed: 0, unanalyzed: 0 };
	const meta = data.meta || {};

	const scoreStatus = meta.score_last
		? meta.score_next
			/* translators: 1: date of the last reading, 2: date of the next scheduled one. */
			? sprintf(__('Last reading %1$s · next automatic reading %2$s', 'ai-rank-suite'), meta.score_last, meta.score_next)
			/* translators: %s: date of the last reading. */
			: sprintf(__('Last reading %s · a new one is taken once a day and whenever you press Recalculate', 'ai-rank-suite'), meta.score_last)
		: null;
	/*
	 * The findings line only moves when an audit finishes, and on a site
	 * with no scheduled audits that is never unless somebody starts one.
	 * The sentence under the card said so; now the card can do it. The
	 * Site Audit screen drives a run one slice at a time from the browser
	 * and resumes a run it finds in progress, so starting here and moving
	 * there is the same thing as pressing its own button.
	 */
	const startAudit = async () => {
		setStarting(true);
		try {
			await api.post('/audit/start', {});
		} catch (e) {
			// The audit screen shows its own error; going there is still right.
		}
		window.location.hash = '/audit';
	};
	const findingsAction = meta.audit_running
		? <Button variant="ghost" onClick={() => { window.location.hash = '/audit'; }}>{__('Open Site Audit', 'ai-rank-suite')}</Button>
		: <Button variant="ghost" onClick={startAudit} disabled={starting}>{starting ? __('Starting…', 'ai-rank-suite') : __('Run an audit now', 'ai-rank-suite')}</Button>;

	const findingsStatus = meta.audit_running
		? __('An audit is running; its findings appear here when it finishes.', 'ai-rank-suite')
		: meta.findings_last
			? meta.audit_next
				/* translators: 1: date of the last completed audit, 2: date of the next scheduled one. */
				? sprintf(__('Last audit %1$s · next scheduled audit %2$s', 'ai-rank-suite'), meta.findings_last, meta.audit_next)
				/* translators: %s: date of the last completed audit. */
				: sprintf(__('Last audit %s · no audit is scheduled. Run one now, or switch on scheduled audits under Site Audit.', 'ai-rank-suite'), meta.findings_last)
			: __('No audit has run yet. Run one now, or switch on scheduled audits under Site Audit.', 'ai-rank-suite');

	// Nothing has ever been measured: three empty cards would be noise.
	if (score.length === 0 && findings.length === 0 && scored.length === 0) return null;

	const lastFinding = findings.length ? findings[findings.length - 1] : null;
	const firstFinding = findings.length > 1 ? findings[0] : null;
	const sev = (key) => ({
		value: lastFinding ? lastFinding[key] : null,
		delta: firstFinding ? lastFinding[key] - firstFinding[key] : null,
	});

	return (
		<div className="grid gap-4">
			{charts.includes('score') && <TrendCard
				title={__('AI search score over time', 'ai-rank-suite')}
				subtitle={__('One point per recalculation — the number that should be rising', 'ai-rank-suite')}
				note={__('One point each time the score was recalculated, by hand or on schedule; the last 180 are kept. The figure in the header compares the newest reading with the oldest one still on this line.', 'ai-rank-suite')}
				series={score}
				status={scoreStatus}
				color="var(--ars-brand)"
				label={__('Score', 'ai-rank-suite')}
				formatValue={(v) => `${v}`}
				bands={[
					{ to: 50, label: __('needs work', 'ai-rank-suite'), tint: 'var(--ars-serious-tonal)' },
					{ to: 80, label: __('solid', 'ai-rank-suite'), tint: 'var(--ars-warning-tonal)' },
					{ to: 100, label: __('strong', 'ai-rank-suite'), tint: 'var(--ars-good-tonal)' },
				]}
				tiles={[
					{
						label: __('Now', 'ai-rank-suite'),
						value: score.length ? score[score.length - 1].value : null,
						delta: score.length > 1 ? score[score.length - 1].value - score[0].value : null,
						color: 'var(--ars-brand)',
					},
					{
						label: __('Best so far', 'ai-rank-suite'),
						value: score.length ? Math.max(...score.map((p) => p.value)) : null,
						delta: null,
						color: 'var(--ars-ink-2)',
					},
				]}
			/>}

			{charts.includes('findings') && <TrendCard
				title={__('Findings over time', 'ai-rank-suite')}
				subtitle={__('One point per audit run — the number that should be falling', 'ai-rank-suite')}
				note={__('One point per completed audit, last 30. A run still crawling is left out until it finishes, so the line cannot dip and then correct itself an hour later.', 'ai-rank-suite')}
				series={findings}
				status={findingsStatus}
				statusAction={findingsAction}
				color="var(--ars-accent)"
				label={__('Open findings', 'ai-rank-suite')}
				formatValue={(v) => `${v}`}
				invert
				breakdown={lastFinding ? [
					{ label: __('Critical', 'ai-rank-suite'), value: lastFinding.critical || 0, color: 'var(--ars-c-red-ink)' },
					{ label: __('Warning', 'ai-rank-suite'), value: lastFinding.warning || 0, color: 'var(--ars-accent)' },
					{ label: __('Notice', 'ai-rank-suite'), value: lastFinding.notice || 0, color: 'var(--ars-brand)' },
				] : null}
				tiles={[
					{ label: __('Critical', 'ai-rank-suite'), ...sev('critical'), color: 'var(--ars-c-red-ink)', invert: true },
					{ label: __('Warning', 'ai-rank-suite'), ...sev('warning'), color: 'var(--ars-accent)', invert: true },
					{ label: __('Notice', 'ai-rank-suite'), ...sev('notice'), color: 'var(--ars-brand)', invert: true },
				]}
			/>}

			{charts.includes('scored') && <TrendCard
				title={__('Pages scored over time', 'ai-rank-suite')}
				subtitle={__('Cumulative, from the day each page was analysed — coverage should keep pace with the site', 'ai-rank-suite')}
				note={__('The running total of pages carrying a score, by the day each one was analysed in your site’s timezone, over the last 60 days. It starts from everything scored before that window, not from zero.', 'ai-rank-suite')}
				series={scored}
				color="var(--ars-good)"
				label={__('Pages scored', 'ai-rank-suite')}
				formatValue={(v) => v.toLocaleString()}
				/*
				 * On the first day there is one reading, so "scored so far"
				 * and "added on the last day" are the same number — and the
				 * reading above already prints it. Three copies of 200 read as
				 * a broken card, so until a second reading exists the tiles
				 * carry what is genuinely unknown: how much of the site is
				 * still unscored, and what share that leaves covered.
				 */
				tiles={
					scored.length > 1
						? [
								{
									label: __('Scored', 'ai-rank-suite'),
									value: scored[scored.length - 1].value,
									delta: scored[scored.length - 1].value - scored[0].value,
									color: 'var(--ars-good)',
								},
								{
									label: __('Added on the last day', 'ai-rank-suite'),
									value: scored[scored.length - 1].value - scored[scored.length - 2].value,
									delta: null,
									color: 'var(--ars-ink-2)',
								},
							]
						: coverage.published > 0
							? [
									{
										label: __('Not scored yet', 'ai-rank-suite'),
										value: coverage.unanalyzed,
										delta: null,
										color: coverage.unanalyzed > 0 ? 'var(--ars-accent)' : 'var(--ars-good)',
									},
									{
										label: __('Coverage', 'ai-rank-suite'),
										value: null,
										text: sprintf(__('%d%%', 'ai-rank-suite'), Math.round((coverage.analyzed / Math.max(1, coverage.published)) * 100)),
										delta: null,
										color: 'var(--ars-ink-2)',
									},
								]
							: []
				}
			/>}
		</div>
	);
}
