import React, { useEffect, useState } from 'react';
import { __, _n, sprintf } from '@wordpress/i18n';
import { api } from '../api';
import { usableTabs } from '../App';
import { Card, Button, Switch, Badge, Notice, MetricGrid, LineChart, Tabs, Panel, PageState, PageSkeleton } from '../components/ui';
import MatomoPanel from '../components/MatomoPanel';
import ConsentPanel from '../components/ConsentPanel';

function Ga4() {
	const [data, setData] = useState(null);
	const [report, setReport] = useState(null);
	const [properties, setProperties] = useState(null);
	const [days, setDays] = useState(28);
	const [busy, setBusy] = useState(false);
	const [saved, setSaved] = useState(false);
	const [error, setError] = useState(null);
	/*
	 * Up here with the rest, not down beside the function that uses it.
	 *
	 * It was declared after `if (!data) return`, so the first render — before
	 * /settings/analytics answers — ran six hooks and the second ran seven.
	 * React counts them and refuses: "Rendered more hooks than during the
	 * previous render" (#310). The whole Measure screen went to the error
	 * boundary the moment the settings arrived, which is to say always.
	 */
	const [picked, setPicked] = useState(null);
	// The raw ids are a repair tool, not the way in. Hidden once a property is
	// configured, the way Search Console hides its property picker.
	const [editIds, setEditIds] = useState(false);

	useEffect(() => {
		api.get('/settings/analytics').then(setData).catch((e) => setError(e.message));
	}, []);

	useEffect(() => {
		if (!data?.settings?.property_id) return;
		setReport(null);
		api.get(`/analytics/report?days=${days}`).then(setReport).catch((e) => setError(e.message));
	}, [data?.settings?.property_id, days]);

	if (!data) return <PageState error={error} />;
	const s = data.settings;
	const update = (patch) => setData({ ...data, settings: { ...s, ...patch } });

	const save = async () => {
		setBusy(true);
		setError(null);
		try {
			setData(await api.put('/settings/analytics', s));
			setSaved(true);
			setTimeout(() => setSaved(false), 2200);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const loadProperties = async () => {
		setBusy(true);
		setError(null);
		try {
			const r = await api.get('/analytics/properties');
			setProperties(r.properties);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	/*
	 * Find this site's own property, rather than listing every property the
	 * account can see. The first real account this met had fifty-five, and a
	 * flat list of fifty-five buttons is not a choice, it is a haystack. The
	 * server matches on the data stream's URL — the only field that actually
	 * says which site a property measures — and hands back the measurement id
	 * with it, so both fields fill from one click.
	 */
	/*
	 * Finding the property IS connecting it.
	 *
	 * /gsc/auto-configure writes the property and measurement id on the server
	 * — the same call the Google callback makes, which is why Search Console
	 * needs no second step. This screen used to drop the answer into local
	 * state and then say "Save to keep it", so the one flow that is automatic
	 * everywhere else ended in a form the customer had to submit. Now it
	 * re-reads the saved settings, and the panel switches to its connected
	 * state on its own.
	 */
	const autoPick = async () => {
		setBusy(true);
		setError(null);
		setPicked(null);
		try {
			const r = await api.post('/gsc/auto-configure', {});
			setPicked(r);
			if (r.ga4_property) {
				setData(await api.get('/settings/analytics'));
				setEditIds(false);
			}
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	/*
	 * Sign-in first, own credentials second.
	 *
	 * /gsc/connect-url builds an OAuth URL from the site's own Google Cloud
	 * credentials and answers 400 airanksuite_gsc_no_credentials when there are none
	 * — which is every customer who has not opened the Cloud console, i.e.
	 * almost all of them. /gsc/signin-url is the one-click path through the
	 * vendor's connect service, and it is what the Search Console screen has
	 * always offered first. Falling back the other way round means the button
	 * works for both kinds of customer.
	 */
	const connectGoogle = async () => {
		setBusy(true);
		setError(null);
		try {
			let url = '';
			try {
				url = (await api.get('/gsc/signin-url')).url || '';
			} catch (e) {
				url = (await api.get('/gsc/connect-url')).url || '';
			}
			if (!url) throw new Error(__('Google sign-in is not available on this site.', 'ai-rank-suite'));
			window.location.href = url;
		} catch (e) {
			setError(e.message);
			setBusy(false);
		}
	};

	const fmt = (n) => (n >= 10000 ? `${(n / 1000).toFixed(1)}k` : String(n));

	return (
		<div className="grid gap-4">
			{error && <Notice tone="serious">{error}</Notice>}

			{report && (
				<>
					<div className="flex justify-end">
						<Tabs
							tabs={[
								{ id: 7, label: __('7 days', 'ai-rank-suite') },
								{ id: 28, label: __('28 days', 'ai-rank-suite') },
								{ id: 90, label: __('90 days', 'ai-rank-suite') },
							]}
							active={days}
							onChange={setDays}
						/>
					</div>
					<MetricGrid
						columns={4}
						metrics={[
							{
								id: 'users',
								label: __('Users', 'ai-rank-suite'),
								hint: __('Distinct people, as GA4 counts them, in the reporting period.', 'ai-rank-suite'),
								value: report.totals.users == null ? '—' : fmt(report.totals.users),
							},
							{
								id: 'sessions',
								label: __('Sessions', 'ai-rank-suite'),
								hint: __('Visits — one person coming back twice counts twice here.', 'ai-rank-suite'),
								value: report.totals.sessions == null ? '—' : fmt(report.totals.sessions),
							},
							{
								id: 'pageviews',
								label: __('Pageviews', 'ai-rank-suite'),
								hint: __('Every page load, including repeat views of the same page.', 'ai-rank-suite'),
								value: report.totals.pageviews == null ? '—' : fmt(report.totals.pageviews),
							},
							{
								id: 'engagement',
								label: __('Engagement rate', 'ai-rank-suite'),
								hint: __('Sessions that lasted, converted, or viewed more than one page — GA4’s replacement for bounce rate.', 'ai-rank-suite'),
								value: report.totals.engagement == null ? '—' : report.totals.engagement,
								unit: report.totals.engagement == null ? '' : '%',
							},
						]}
					/>
					<Card title={__('Users over time', 'ai-rank-suite')}>
						<LineChart label={__('Active users', 'ai-rank-suite')} color="var(--ars-series-1)" data={report.series} />
					</Card>
					{report.pages.length > 0 && (
						<Card title={__('Top pages', 'ai-rank-suite')}>
							<div className="ars-table-wrap"><table className="ars-table">
								<thead>
									<tr>
										<th>{__('Path', 'ai-rank-suite')}</th>
										<th className="is-num">{__('Pageviews', 'ai-rank-suite')}</th>
										<th className="is-num">{__('Users', 'ai-rank-suite')}</th>
									</tr>
								</thead>
								<tbody>
									{report.pages.map((p) => (
										<tr key={p.path}>
											<td className="ars-mono" style={{ maxWidth: 340 }} title={p.path}>
												<span className="ars-url">{p.path}</span>
											</td>
											<td className="is-num">{p.pageviews}</td>
											<td className="is-num">{p.users}</td>
										</tr>
									))}
								</tbody>
							</table></div>
						</Card>
					)}
				</>
			)}

			<Panel
				title={__('Google Analytics settings', 'ai-rank-suite')}
				note={s.enabled ? (s.measurement_id || __('Tracking on', 'ai-rank-suite')) : __('Tracking off', 'ai-rank-suite')}
				/* Open only while there is nothing to report yet. Once the tag
				   is live and numbers exist, this is a measurement ID nobody
				   needs to look at again — and it used to be the first thing
				   on the screen, above the numbers people came for. */
				defaultOpen={!report}
			>
				<Card
					title={__('Google Analytics 4', 'ai-rank-suite')}
					subtitle={__('Adds the tracking tag and pulls reports through your existing Google connection', 'ai-rank-suite')}
					action={
						<div className="flex items-center gap-2">
							{saved && <Badge tone="good">✓ {__('Saved', 'ai-rank-suite')}</Badge>}
							{/* "Tracking on" beside an off switch reads as a status claim, not a
							    control label. Every other module toggle in the product states the
							    current state — Active, Enabled — so this one does too. */}
							<Switch
								checked={!!s.enabled}
								onChange={(v) => update({ enabled: v })}
								label={s.enabled ? __('Tracking on', 'ai-rank-suite') : __('Tracking off', 'ai-rank-suite')}
							/>
						</div>
					}
				>
					<div className="grid gap-4">
						{/* Three states, one at a time — the same shape as the
						    Search Console panel. Configured: a sentence saying
						    what it is connected to. Connected to Google but no
						    property yet: one button. Not connected: the button
						    that connects, rather than a note telling somebody
						    to go and find another screen. */}
						{!data.connected ? (
							<div className="ars-connect">
								<div className="min-w-0">
									<div className="font-semibold text-[13.5px]">{__('Not connected to Google', 'ai-rank-suite')}</div>
									<p className="m-0 t-caption">{__('One sign-in covers Search Console and Analytics. Nothing is typed in by hand.', 'ai-rank-suite')}</p>
								</div>
								<Button variant="primary" busy={busy} onClick={connectGoogle}>{__('Sign in with Google', 'ai-rank-suite')}</Button>
							</div>
						) : !s.property_id ? (
							<div className="ars-connect">
								<div className="min-w-0">
									<div className="font-semibold text-[13.5px]">{__('Google is connected — no property picked yet', 'ai-rank-suite')}</div>
									<p className="m-0 t-caption">{__('Matches your domain against the data streams in your Google account and saves the match.', 'ai-rank-suite')}</p>
								</div>
								<span className="flex items-center gap-2">
									<Button variant="primary" busy={busy} onClick={autoPick}>{__('Find my property', 'ai-rank-suite')}</Button>
									<Button variant="ghost" busy={busy} onClick={loadProperties}>{__('List all', 'ai-rank-suite')}</Button>
								</span>
							</div>
						) : (
							<div className="ars-connect is-good">
								<div className="min-w-0">
									<div className="font-semibold text-[13.5px]">
										{sprintf(
											/* translators: %s: GA4 property id. */
											__('Connected to property %s', 'ai-rank-suite'),
											s.property_id
										)}
									</div>
									<p className="m-0 t-caption">
										{s.measurement_id
											? sprintf(
													/* translators: %s: GA4 measurement id. */
													__('Reports read through your Google sign-in. Tracking tag: %s', 'ai-rank-suite'),
													s.measurement_id
												)
											: __('Reports read through your Google sign-in. No tracking tag set — reporting works without one.', 'ai-rank-suite')}
									</p>
								</div>
								<span className="flex items-center gap-2">
									<Button variant="ghost" busy={busy} onClick={autoPick}>{__('Re-detect', 'ai-rank-suite')}</Button>
									<button type="button" className="ars-linkbtn" onClick={() => setEditIds((v) => !v)}>
										{editIds ? __('Hide IDs', 'ai-rank-suite') : __('Change IDs', 'ai-rank-suite')}
									</button>
								</span>
							</div>
						)}

						{(editIds || (data.connected && !s.property_id && properties)) && (
							<div className="grid gap-3 sm:grid-cols-2">
								<div>
									<label className="block font-medium mb-1">{__('Measurement ID', 'ai-rank-suite')}</label>
									<input
										type="text"
										placeholder={__('G-XXXXXXXXXX', 'ai-rank-suite')}
										value={s.measurement_id}
										onChange={(e) => update({ measurement_id: e.target.value })}
									/>
								</div>
								<div>
									<label className="block font-medium mb-1">{__('Property ID (for reports)', 'ai-rank-suite')}</label>
									<input
										type="text"
										placeholder="123456789"
										value={s.property_id}
										onChange={(e) => update({ property_id: e.target.value })}
									/>
								</div>
							</div>
						)}

						{picked && !picked.ga4_property && (
							<Notice tone="warning">
								{__('No property in this Google account points at this site. Use “List all” and pick one, or create a web data stream for this domain in Analytics first.', 'ai-rank-suite')}
							</Notice>
						)}

						{properties && (
							<div className="grid gap-1.5">
								{properties.length === 0 && <p className="m-0 text-[13px]">{__('No GA4 properties found for this account.', 'ai-rank-suite')}</p>}
								{properties.map((p) => (
									<Button key={p.id} variant="ghost" onClick={() => update({ property_id: p.id })}>
										{p.name} — {p.id}
									</Button>
								))}
							</div>
						)}


						<Switch checked={!!s.anonymize_ip} onChange={(v) => update({ anonymize_ip: v })} label={__('Anonymize IP addresses', 'ai-rank-suite')} />
						<Switch
							checked={!!s.exclude_loggedin}
							onChange={(v) => update({ exclude_loggedin: v })}
							label={__('Do not track logged-in users of these roles', 'ai-rank-suite')}
						/>
						{/* The role checkboxes hung loose under the toggle they belong to,
						    at the same indent, in a different control language. A tonal panel
						    indented under the switch says "these are its options". */}
						{s.exclude_loggedin && (
							<div
								className="flex gap-x-4 gap-y-2 flex-wrap text-[12.5px] ars-r-sm px-3 py-2.5 ml-[52px]"
								style={{ background: 'var(--ars-surface-2)' }}
							>
								{data.roles.map((r) => (
									<label key={r.slug} className="inline-flex items-center gap-1.5">
										<input
											type="checkbox"
											checked={s.exclude_roles.includes(r.slug)}
											onChange={(e) =>
												update({
													exclude_roles: e.target.checked
														? [...s.exclude_roles, r.slug]
														: s.exclude_roles.filter((x) => x !== r.slug),
												})
											}
										/>
										{r.name}
									</label>
								))}
							</div>
						)}

						<p className="m-0 t-caption">{__('Consent for all analytics tags lives under the “GDPR / Consent” tab — categories, EU/US behaviour and Google Consent Mode v2.', 'ai-rank-suite')}</p>

						<div>
							<Button onClick={save} busy={busy}>{__('Save', 'ai-rank-suite')}</Button>
						</div>
					</div>
				</Card>
			</Panel>
		</div>
	);
}

function Reports() {
	const [data, setData] = useState(null);
	const [busy, setBusy] = useState(false);
	const [saved, setSaved] = useState(false);
	const [sent, setSent] = useState(null);
	const [error, setError] = useState(null);

	useEffect(() => {
		api.get('/settings/reports').then(setData).catch((e) => setError(e.message));
	}, []);

	if (!data) return <PageState error={error} />;
	const s = data.settings;
	const update = (patch) => setData({ ...data, settings: { ...s, ...patch } });

	const save = async () => {
		setBusy(true);
		setError(null);
		try {
			setData(await api.put('/settings/reports', s));
			setSaved(true);
			setTimeout(() => setSaved(false), 2200);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const sendNow = async () => {
		setBusy(true);
		setError(null);
		try {
			const r = await api.post('/reports/send');
			setSent(r);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	return (
		<div className="ars-page">
			{error && <Notice tone="serious">{error}</Notice>}
			<Card
				title={__('Email reports', 'ai-rank-suite')}
				subtitle={__('Scores, search performance and keyword movement — straight to your inbox or your client\'s', 'ai-rank-suite')}
				action={
					<div className="flex items-center gap-2">
						{saved && <Badge tone="good">✓ {__('Saved', 'ai-rank-suite')}</Badge>}
						<Switch checked={!!s.enabled} onChange={(v) => update({ enabled: v })} label={__('Enabled', 'ai-rank-suite')} />
					</div>
				}
			>
				<div className="grid gap-4">
					<div className="grid gap-3 sm:grid-cols-2">
						<div>
							<label className="block font-medium mb-1">{__('Frequency', 'ai-rank-suite')}</label>
							<select value={s.frequency} onChange={(e) => update({ frequency: e.target.value })}>
								<option value="weekly">{__('Weekly', 'ai-rank-suite')}</option>
								<option value="monthly">{__('Monthly (on the 1st)', 'ai-rank-suite')}</option>
							</select>
						</div>
						<div>
							<label className="block font-medium mb-1">{__('Recipients', 'ai-rank-suite')}</label>
							<input
								type="text"
								placeholder={__('you@example.com, client@example.com', 'ai-rank-suite')}
								value={s.recipients}
								onChange={(e) => update({ recipients: e.target.value })}
							/>
						</div>
					</div>

					<div className="ars-r-md p-3 grid gap-3" style={{ background: 'var(--ars-surface-2)' }}>
						<div className="font-semibold text-[13px]">{__('White label', 'ai-rank-suite')}</div>
						<div className="grid gap-3 sm:grid-cols-2">
							<div>
								<label className="block font-medium mb-1">{__('Brand name', 'ai-rank-suite')}</label>
								<input type="text" value={s.brand_name} onChange={(e) => update({ brand_name: e.target.value })} />
							</div>
							<div>
								<label className="block font-medium mb-1">{__('Sender name', 'ai-rank-suite')}</label>
								<input type="text" value={s.from_name} onChange={(e) => update({ from_name: e.target.value })} />
							</div>
							<div>
								<label className="block font-medium mb-1">{__('Logo URL', 'ai-rank-suite')}</label>
								<input type="text" value={s.logo_url} onChange={(e) => update({ logo_url: e.target.value })} />
							</div>
							<div>
								<label className="block font-medium mb-1">{__('Accent color', 'ai-rank-suite')}</label>
								<input type="text" value={s.accent} onChange={(e) => update({ accent: e.target.value })} />
							</div>
						</div>
						<Switch
							checked={!!s.white_label}
							onChange={(v) => update({ white_label: v })}
							label={__('Replace the plugin footer with your brand', 'ai-rank-suite')}
						/>
					</div>

					<div className="flex items-center gap-2 flex-wrap">
						<Button onClick={save} busy={busy}>{__('Save', 'ai-rank-suite')}</Button>
						<Button variant="ghost" onClick={sendNow} busy={busy}>{__('Send test report now', 'ai-rank-suite')}</Button>
						<a
							href={`${window.airanksuiteData.restUrl}/reports/preview?_wpnonce=${window.airanksuiteData.nonce}`}
							target="_blank"
							rel="noreferrer"
							className="text-[12.5px]"
						>{__('Preview data ↗', 'ai-rank-suite')}</a>
						{sent && <Badge tone="good">✓ {sprintf(_n('Sent to %d recipient', 'Sent to %d recipients', sent.sent, 'ai-rank-suite'), sent.sent)}</Badge>}
					</div>

					{data.next_run > 0 && (
						<p className="m-0 text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
							{sprintf(__('Next report: %s', 'ai-rank-suite'), new Date(data.next_run * 1000).toLocaleString())}
							{data.sent_at > 0 && sprintf(__(' · last sent %s', 'ai-rank-suite'), new Date(data.sent_at * 1000).toLocaleString())}
						</p>
					)}
				</div>
			</Card>
		</div>
	);
}

function Speed() {
	const [data, setData] = useState(null);
	const [key, setKey] = useState('');
	const [busy, setBusy] = useState(false);
	const [saved, setSaved] = useState(false);
	const [error, setError] = useState(null);
	// Every hook above the early return, always. See Ga4() for the crash this
	// rule exists to prevent.
	const [measuring, setMeasuring] = useState(false);

	useEffect(() => {
		api.get('/settings/pagespeed').then(setData).catch((e) => setError(e.message));
	}, []);

	if (!data) return <PageState error={error} />;

	const save = async () => {
		setBusy(true);
		setError(null);
		try {
			setData(await api.put('/settings/pagespeed', { strategy: data.settings.strategy, api_key: key }));
			setKey('');
			setSaved(true);
			setTimeout(() => setSaved(false), 2200);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	/*
	 * The screen measures something.
	 *
	 * It used to be a form that stored an API key and a strategy, and every
	 * number it configured appeared somewhere else — inside one page's
	 * analysis panel. So a customer set it up, pressed Save, and the screen
	 * called "PageSpeed Insights" never showed a speed. It measures the front
	 * page on demand now and keeps the last reading.
	 */
	const measure = async () => {
		setMeasuring(true);
		setError(null);
		try {
			const r = await api.post('/pagespeed/home', {});
			setData({ ...data, home: r.result });
		} catch (e) {
			setError(e.message);
		} finally {
			setMeasuring(false);
		}
	};

	const home = data.home;
	const band = (n) => (n >= 90 ? 'good' : n >= 50 ? 'warn' : 'bad');

	return (
		<div className="ars-page">
			{error && <Notice tone="serious">{error}</Notice>}
			<Card
				title={__('PageSpeed Insights', 'ai-rank-suite')}
				subtitle={__('Core Web Vitals, measured by Google on your live front page', 'ai-rank-suite')}
				action={
					<Button variant="primary" busy={measuring} onClick={measure}>
						{__('Measure now', 'ai-rank-suite')}
					</Button>
				}
			>
				{home ? (
					<div className="grid gap-3">
						<div className="flex items-center gap-4 flex-wrap">
							<span className={`ars-scorerow-score is-${band(home.score ?? 0)}`} style={{ fontSize: 22, minWidth: 64, padding: '10px 0' }}>
								{home.score ?? '—'}
							</span>
							<span className="min-w-0">
								<span className="block font-semibold text-[13.5px]">
									{sprintf(
										/* translators: %s: mobile or desktop. */
										__('Performance score, measured as %s', 'ai-rank-suite'),
										'desktop' === home.strategy ? __('desktop', 'ai-rank-suite') : __('mobile', 'ai-rank-suite')
									)}
								</span>
								<span className="t-caption">{data.home_url}</span>
							</span>
						</div>
						<MetricGrid
							columns={4}
							metrics={[
								{ id: 'lcp', label: 'LCP', hint: __('Largest Contentful Paint — when the main thing on the page has drawn. Google wants under 2.5s.', 'ai-rank-suite'), value: home.lcp || '—' },
								{ id: 'cls', label: 'CLS', hint: __('Cumulative Layout Shift — how much the page jumps while it loads. Under 0.1.', 'ai-rank-suite'), value: home.cls || '—' },
								{ id: 'tbt', label: 'TBT', hint: __('Total Blocking Time — how long the page ignores a tap while scripts run.', 'ai-rank-suite'), value: home.tbt || '—' },
								{ id: 'fcp', label: 'FCP', hint: __('First Contentful Paint — when the first text or image appears.', 'ai-rank-suite'), value: home.fcp || '—' },
							]}
						/>
					</div>
				) : (
					<p className="ars-empty-band">
						{__('Nothing measured yet. “Measure now” asks Google to load your front page and report what a visitor would wait for.', 'ai-rank-suite')}
					</p>
				)}
			</Card>
			<Card
				title={__('Settings', 'ai-rank-suite')}
				subtitle={__('How the measurement runs, and the key it runs on', 'ai-rank-suite')}
				action={saved && <Badge tone="good">✓ {__('Saved', 'ai-rank-suite')}</Badge>}
			>
				<div className="grid gap-3">
					<div className="sm:max-w-[260px]">
						<label className="block font-medium mb-1">{__('Measure as', 'ai-rank-suite')}</label>
						<select
							value={data.settings.strategy}
							onChange={(e) => setData({ ...data, settings: { ...data.settings, strategy: e.target.value } })}
						>
							<option value="mobile">{__('Mobile (what Google ranks on)', 'ai-rank-suite')}</option>
							<option value="desktop">{__('Desktop', 'ai-rank-suite')}</option>
						</select>
					</div>
					<div>
						<label className="block font-medium mb-1">
							{__('Google API key (optional)', 'ai-rank-suite')} {data.has_key && <Badge tone="good">✓ {__('stored', 'ai-rank-suite')}</Badge>}
						</label>
						<input
							type="password"
							placeholder={data.has_key ? __('•••••••• (leave empty to keep)', 'ai-rank-suite') : __('Raises the request quota', 'ai-rank-suite')}
							value={key}
							onChange={(e) => setKey(e.target.value)}
						/>
					</div>
					<div>
						<Button onClick={save} busy={busy}>{__('Save', 'ai-rank-suite')}</Button>
					</div>
					<p className="m-0 text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
						{__('Works without a key at low volume. Each measurement takes up to a minute — Google runs a real Lighthouse test.', 'ai-rank-suite')}
					</p>
				</div>
			</Card>
		</div>
	);
}

/**
 * Microsoft Clarity.
 *
 * The tag has shipped since the analytics module existed, but its one field
 * sat inside the Google Analytics panel — so the tab strip listed five tools
 * and Clarity was not one of them, and people concluded it was missing. It is
 * its own product with its own account; it gets its own tab.
 */
function ClarityTab() {
	const [data, setData] = useState(null);
	const [draft, setDraft] = useState(null);
	const [saving, setSaving] = useState(false);
	const [error, setError] = useState(null);

	useEffect(() => {
		api.get('/settings/analytics').then(setData).catch((e) => setError(e.message));
	}, []);

	if (!data) return <PageState error={error} />;

	const s = data.settings || {};
	const id = (s.clarity_id || '').trim();
	const value = draft === null ? s.clarity_id || '' : draft;

	// The whole settings object goes back, as every other panel here does:
	// the endpoint replaces what it is given.
	const save = async (patch) => {
		setSaving(true);
		setError(null);
		try {
			setData(await api.put('/settings/analytics', { ...s, ...patch }));
		} catch (e) {
			setError(e.message);
		} finally {
			setSaving(false);
		}
	};

	return (
		<div className="grid gap-4">
			{error && <Notice tone="serious">{error}</Notice>}
			<Card
				title={__('Microsoft Clarity', 'ai-rank-suite')}
				subtitle={__('Free heatmaps and session recordings. No sampling, no row limit, and it does not count against any Google quota.', 'ai-rank-suite')}
				action={
					id ? <Badge tone="good">{__('Tag is live', 'ai-rank-suite')}</Badge> : <Badge tone="neutral">{__('Not set up', 'ai-rank-suite')}</Badge>
				}
			>
				<div className="grid gap-3 max-w-[560px]">
					<div>
						<label className="block font-medium mb-1" htmlFor="airanksuite-clarity-id">{__('Project ID', 'ai-rank-suite')}</label>
						<div className="flex gap-2">
							<input
								id="airanksuite-clarity-id"
								type="text"
								placeholder="abcd1234ef"
								value={value}
								onChange={(e) => setDraft(e.target.value)}
								style={{ flex: 1 }}
							/>
							<Button
								busy={saving}
								disabled={draft === null || draft.trim() === (s.clarity_id || '').trim()}
								onClick={async () => {
									await save({ clarity_id: draft.trim() });
									setDraft(null);
								}}
							>{__('Save', 'ai-rank-suite')}</Button>
							{draft !== null && (
								<Button variant="ghost" onClick={() => setDraft(null)}>{__('Cancel', 'ai-rank-suite')}</Button>
							)}
						</div>
						<div className="text-[11.5px] mt-1.5" style={{ color: 'var(--ars-ink-3)' }}>{__('In Clarity: Settings → Overview → Project ID. Ten characters, letters and digits.', 'ai-rank-suite')}</div>
					</div>

					{id && (
						<div className="flex items-center gap-2 flex-wrap">
							<a
								className="ars-btn ars-btn-ghost"
								href={`https://clarity.microsoft.com/projects/view/${encodeURIComponent(id)}/dashboard`}
								target="_blank"
								rel="noreferrer noopener"
							>{__('Open this project in Clarity ↗', 'ai-rank-suite')}</a>
							<Button
								variant="ghost"
								onClick={async () => {
									await save({ clarity_id: '' });
									setDraft(null);
								}}
							>{__('Remove', 'ai-rank-suite')}</Button>
						</div>
					)}
				</div>
			</Card>

			<Card title={__('What the tag does on your site', 'ai-rank-suite')} subtitle={__('The same rules the other analytics tags follow', 'ai-rank-suite')}>
				<ul className="grid gap-2 m-0 pl-4 text-[13px]" style={{ color: 'var(--ars-ink-2)' }}>
					<li>{__('Loads only for visitors — logged-in users and the excluded roles never see it, so your own clicks stay out of the recordings.', 'ai-rank-suite')}</li>
					<li>{__('Waits for consent where consent is required: with the cookie bar on, the tag is held until the visitor accepts the analytics category.', 'ai-rank-suite')}</li>
					<li>{__('Loads asynchronously from clarity.ms and blocks nothing on the page.', 'ai-rank-suite')}</li>
					<li>{__('Records interactions, not form values — Clarity masks input content by default.', 'ai-rank-suite')}</li>
				</ul>
			</Card>
		</div>
	);
}

export default function Analytics({ tab = 'ga4', onTab }) {
	const views = { ga4: Ga4, matomo: MatomoTab, clarity: ClarityTab, consent: ConsentPanel, reports: Reports, speed: Speed };
	const View = views[tab] || Ga4;

	return (
		<div className="grid gap-4">
			{/* Scheduled reports are their own Pro module. */}
			{(() => {
				const { tabs: anTabs, active: anActive } = usableTabs(
					[
						{ id: 'ga4', label: __('Google Analytics', 'ai-rank-suite') },
						{ id: 'matomo', label: __('Matomo', 'ai-rank-suite') },
						{ id: 'clarity', label: __('Clarity', 'ai-rank-suite') },
						{ id: 'consent', label: __('GDPR / Consent', 'ai-rank-suite') },
						{ id: 'reports', label: __('Reports', 'ai-rank-suite'), needs: 'reports' },
						{ id: 'speed', label: __('PageSpeed', 'ai-rank-suite') },
					],
					tab
				);
				if (anActive !== tab) {
					setTimeout(() => onTab(anActive), 0);
				}
				return <Tabs tabs={anTabs} active={anActive} onChange={onTab} />;
			})()}
			<View />
			<LocalData />
		</div>
	);
}

/**
 * What the plugin keeps in the site's own database, and the controls over
 * it: how long, refresh now, delete. Sits folded at the bottom of every
 * analytics tab because it is about all of them at once — and because on a
 * good day nobody needs to open it.
 */
function LocalData() {
	const [data, setData] = useState(null);
	const [retention, setRetention] = useState('');
	const [busy, setBusy] = useState(false);
	const [saved, setSaved] = useState(false);
	const [syncError, setSyncError] = useState(null);
	const [error, setError] = useState(null);

	useEffect(() => {
		// A 403 here just means "not an administrator" — then there is no
		// panel to show, not an error to report.
		api.get('/analytics/database')
			.then((d) => {
				setData(d);
				setRetention(d.retention_days);
			})
			.catch(() => {});
	}, []);

	if (!data) return <PageSkeleton blocks={3} label={__('Loading analytics', 'ai-rank-suite')} />;

	const ranks = data.tables.find((t) => t.key === 'ranks');
	const mentions = data.tables.find((t) => t.key === 'mentions');
	const num = (n) => (n == null ? '—' : n);
	const size = (b) => (b == null ? '—' : b >= 1048576 ? `${(b / 1048576).toFixed(1)} MB` : `${Math.max(1, Math.round(b / 1024))} KB`);

	const run = async (fn) => {
		setBusy(true);
		setError(null);
		try {
			await fn();
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const save = () =>
		run(async () => {
			const days = Math.min(365, Math.max(30, Number(retention) || 90));
			const d = await api.put('/analytics/database', { retention_days: days });
			setData(d);
			setRetention(d.retention_days);
			setSaved(true);
			setTimeout(() => setSaved(false), 2200);
		});

	const refresh = () =>
		run(async () => {
			setSyncError(null);
			const d = await api.post('/analytics/database/refresh');
			setData(d);
			if (d.sync_error) setSyncError(d.sync_error);
		});

	const purge = (target) => run(async () => setData(await api.post('/analytics/database/purge', { target })));

	return (
		<Panel title={__('Local data', 'ai-rank-suite')} note={sprintf(_n('%d day kept', '%d days kept', data.retention_days, 'ai-rank-suite'), data.retention_days)} defaultOpen={false}>
			<div className="grid gap-4">
				{error && <Notice tone="serious">{error}</Notice>}
				{syncError && <Notice tone="warning">{sprintf(__('Caches were cleared, but the rank sync reported: %s', 'ai-rank-suite'), syncError)}</Notice>}

				<MetricGrid
					columns={3}
					metrics={[
						{
							id: 'ranks',
							label: __('Rank snapshots', 'ai-rank-suite'),
							hint: __('One row per tracked keyword per day, pulled from your Search Console data.', 'ai-rank-suite'),
							value: num(ranks?.rows),
							sub: ranks ? sprintf(__('%1$s days · %2$s', 'ai-rank-suite'), num(ranks.days), size(ranks.bytes)) : '—',
						},
						{
							id: 'mentions',
							label: __('Answer simulation runs', 'ai-rank-suite'),
							hint: __('Kept for good: a past AI answer cannot be queried again for a past date, so this history can never be rebuilt — and is never deleted.', 'ai-rank-suite'),
							value: num(mentions?.rows),
							sub: mentions ? size(mentions.bytes) : '—',
						},
						{
							id: 'cache',
							label: __('Cached reports', 'ai-rank-suite'),
							hint: __('Stored copies of Google and AI responses. Safe to clear — each one rebuilds on its next view.', 'ai-rank-suite'),
							value: num(data.cache_entries),
						},
					]}
				/>

				<div className="flex items-end gap-2 flex-wrap">
					<div className="sm:max-w-[160px]">
						<label className="block font-medium mb-1">{__('Keep snapshots for (days)', 'ai-rank-suite')}</label>
						<input
							type="number"
							min={30}
							max={365}
							value={retention}
							onChange={(e) => setRetention(e.target.value)}
						/>
					</div>
					<Button onClick={save} busy={busy}>{__('Save', 'ai-rank-suite')}</Button>
					{saved && <Badge tone="good">✓ {__('Saved', 'ai-rank-suite')}</Badge>}
				</div>

				<div className="flex items-center gap-2 flex-wrap">
					<Button onClick={refresh} busy={busy}>{__('Update data now', 'ai-rank-suite')}</Button>
					<Button
						variant="ghost"
						busy={busy}
						onClick={() =>
							window.confirm(
								__('Delete every stored rank snapshot? Your keyword list stays, but the position history is gone for good.', 'ai-rank-suite')
							) && purge('ranks')
						}
					>{__('Delete rank snapshots', 'ai-rank-suite')}</Button>
					<Button variant="ghost" busy={busy} onClick={() => purge('cache')}>{__('Clear cached reports', 'ai-rank-suite')}</Button>
				</div>
			</div>
		</Panel>
	);
}

/**
 * The Matomo tab owns its settings round-trip, because the analytics settings
 * blob is shared with GA4 and a partial PUT would wipe whichever half was not
 * on screen.
 */
function MatomoTab() {
	const [settings, setSettings] = useState(null);
	const [saving, setSaving] = useState(false);
	const [error, setError] = useState(null);

	useEffect(() => {
		api
			.get('/settings/analytics')
			.then((d) => setSettings(d.settings))
			.catch((e) => setError(e.message));
	}, []);

	if (error) return <Notice tone="serious">{error}</Notice>;
	if (!settings) return <Card>{__('Loading…', 'ai-rank-suite')}</Card>;

	const save = async (next) => {
		setSaving(true);
		try {
			// The whole blob, merged — GA4's fields live in the same option and
			// sending only the Matomo half would clear them.
			const d = await api.put('/settings/analytics', { ...settings, ...next });
			setSettings(d.settings);
		} finally {
			setSaving(false);
		}
	};

	return <MatomoPanel settings={settings} onSave={save} saving={saving} />;
}
