import React, { useEffect, useState } from 'react';
import { __, _n, sprintf } from '@wordpress/i18n';
import { api } from '../api';
import { isPro, isProBuild, licenseUrl } from '../components/Pro';
import AiAtWork from '../components/AiAtWork';
import {
	Card,
	StatusBadge,
	ScorePill,
	Button,
	Notice,
	Panel,
	ScoreRow,
	ScoreRing,
} from '../components/ui';
import DashboardTrends from '../components/DashboardTrends';


const go = (hash) => {
	window.location.hash = hash;
};


export default function Dashboard() {
	const [data, setData] = useState(null);
	const [kpis, setKpis] = useState(null);
	const [content, setContent] = useState(null);
	const [setup, setSetup] = useState(null);
	const [busy, setBusy] = useState(false);
	const [error, setError] = useState(null);
	const [trendRevision, setTrendRevision] = useState(0);
	// Which area column is drilled into, if any.
	const [area, setArea] = useState(null);

	useEffect(() => {
		// One round trip instead of four. The single routes still exist and
		// recalc still uses them — if /boot is missing (an older server under
		// a newer bundle, mid-update), fall back to exactly the old behaviour.
		api
			.get('/boot')
			.then((b) => {
				if (b.score) setData(b.score); else setError(__('The score could not be loaded.', 'ai-rank-suite'));
				setKpis(b.kpis);
				setContent(b.content);
				setSetup(b.setup);
			})
			.catch(() => {
				api.get('/score').then(setData).catch((e) => setError(e.message));
				api.get('/dashboard/kpis').then(setKpis).catch(() => {});
				api.get('/score/content').then(setContent).catch(() => {});
				api.get('/setup/status').then(setSetup).catch(() => {});
			});
	}, []);

	const recalc = async () => {
		setBusy(true);
		setError(null);
		try {
			setData(await api.post('/score/recalculate'));
			setKpis(await api.get('/dashboard/kpis'));
			setTrendRevision((revision) => revision + 1);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const checks = data?.checks || [];
	const todo = checks.filter((c) => c.status !== 'pass');
	const overall = data?.dimensions?.overall;

	/**
	 * What this site most needs, in one sentence.
	 *
	 * Every branch names a number that appears further down the page, so the
	 * claim can be checked rather than believed — and every branch offers the
	 * one control that resolves it.
	 */
	const verdict = (() => {
		const k2 = kpis || {};
		const broken = (k2.links || {}).broken ?? 0;
		const analysed = (k2.content || {}).analyzed ?? 0;
		const failing = todo.filter((c) => c.status === 'fail').length;

		/*
		 * Read each fact from the endpoint that owns it.
		 *
		 * These two used to come off the KPI payload as `ai.engines` and
		 * `content.missing_desc` — neither of which that endpoint has ever
		 * returned. Both read as 0 on every site, so the advisor told everyone
		 * their AI engine was disconnected no matter how many keys they had
		 * stored, and the missing-description branch could never fire at all.
		 * `/setup/status` is the same has_key() the AI screen shows, and
		 * `/score/content` is where missing_desc actually lives.
		 */
		const aiConnected = setup ? !!setup.ai : null;
		const missing = content?.missing_desc ?? 0;

		// `null` means setup has not answered yet. Saying nothing for one tick
		// beats accusing a configured site of having no engine.
		if (aiConnected === false) {
			return {
				tone: 'warning',
				text: __('No AI engine is connected, so the half of this plugin that writes and measures cannot run.', 'ai-rank-suite'),
				why: __('Meta descriptions, FAQs, the autopilot and every answer simulation need a model. One key, stored encrypted on your own server, switches all of them on.', 'ai-rank-suite'),
				action: __('Connect an engine', 'ai-rank-suite'),
				href: '/ai',
			};
		}
		/*
		 * The audit's findings, in the verdict as well as the tile.
		 *
		 * The chain read site checks, broken links and missing descriptions
		 * and then declared "no failing check, no broken link and every page
		 * described" — on a site whose audit, one click away, held 43 warnings.
		 * Critical findings outrank everything but a disconnected engine;
		 * warnings sit below a broken link, which costs traffic today.
		 */
		const audit = k2.audit || {};
		if ((audit.critical ?? 0) > 0) {
			return {
				tone: 'bad',
				text: sprintf(_n('%d critical audit finding needs attention.', '%d critical audit findings need attention.', audit.critical, 'ai-rank-suite'), audit.critical),
				why: __('Critical means a page is blocked, duplicated or unreadable to a crawler. Nothing else on this screen matters until these are gone.', 'ai-rank-suite'),
				action: __('Open the audit', 'ai-rank-suite'),
				href: '/audit',
			};
		}
		if (failing > 0) {
			return {
				tone: 'bad',
				// sprintf + _n rather than a template literal: a sentence
				// assembled from fragments cannot be translated, and German
				// puts the verb in a different place anyway.
				text: sprintf(_n('%d check is failing across the site.', '%d checks are failing across the site.', failing, 'ai-rank-suite'), failing),
				why: __('These are the ones that cost traffic today rather than eventually. Next best actions below lists them in the order worth fixing.', 'ai-rank-suite'),
				action: __('Open the audit', 'ai-rank-suite'),
				href: '/audit',
			};
		}
		if (broken > 0) {
			return {
				tone: 'warning',
				text: sprintf(_n('%d link is broken on this site.', '%d links are broken on this site.', broken, 'ai-rank-suite'), broken),
				why: __('A dead link is a trust signal pointing the wrong way, and crawlers spend budget rediscovering it every time.', 'ai-rank-suite'),
				action: __('Open the checker', 'ai-rank-suite'),
				href: '/links',
			};
		}
		if ((audit.warning ?? 0) > 0) {
			return {
				tone: 'warning',
				text: sprintf(_n('The audit found %d warning across the site.', 'The audit found %d warnings across the site.', audit.warning, 'ai-rank-suite'), audit.warning),
				why: __('Thin content, duplicate descriptions and unlinked pages are the usual weight. Work by rule, not by page: one fix clears hundreds.', 'ai-rank-suite'),
				action: __('Open the audit', 'ai-rank-suite'),
				href: '/audit',
			};
		}
		if (missing > 0) {
			return {
				tone: 'warning',
				text: sprintf(_n('%d published page has no meta description.', '%d published pages have no meta description.', missing, 'ai-rank-suite'), missing),
				why: __('Without one Google writes the snippet from whatever sentence it scrapes, and llms.txt has nothing to describe the page with. The autopilot can write all of them in one run.', 'ai-rank-suite'),
				action: __('Let the autopilot write them', 'ai-rank-suite'),
				href: '/autopilot',
			};
		}
		if (analysed === 0) {
			return {
				tone: 'neutral',
				text: __('Nothing has been analysed yet, so there is nothing to score.', 'ai-rank-suite'),
				why: __('The audit walks your published content and stores a result per page. Everything else on this screen reads from those results.', 'ai-rank-suite'),
				action: __('Run the audit', 'ai-rank-suite'),
				href: '/audit',
			};
		}
		if (overall != null && overall >= 80) {
			return {
				tone: 'good',
				text: sprintf(__('Your site scores %d out of 100 for AI search.', 'ai-rank-suite'), overall),
				why: __('No failing check, no broken link, no open warning and every page described. Keep an eye on the answer-simulation trend — that is the number that moves last.', 'ai-rank-suite'),
				action: __('See what changed', 'ai-rank-suite'),
				href: '/insights',
			};
		}
		return {
			tone: 'neutral',
			text: overall == null ? __('Ready when you are.', 'ai-rank-suite') : sprintf(__('Your site scores %d out of 100 for AI search.', 'ai-rank-suite'), overall),
			why: __('Nothing is broken. The fastest way up from here is the weakest dimension below — the same fix repeated across pages beats a perfect single page.', 'ai-rank-suite'),
			action: __('See where it is weak', 'ai-rank-suite'),
			href: '/audit',
		};
	})();

	const k = kpis || {};
	const vis = k.visibility || {};
	const search = k.search || {};
	const links = k.links || {};
	const idx = k.indexing || {};
	const cnt = k.content || {};
	const commerce = k.commerce || {};
	const health = k.health || {};

	/*
	 * A dashboard is a work queue before it is a report. Keep this deliberately
	 * short: once three decisions are visible, a fourth is no longer a priority.
	 * The full evidence remains in the folded metrics and audit below.
	 */
	const priorities = [
		...(setup && !setup.ai
			? [{ label: __('Connect an AI engine', 'ai-rank-suite'), detail: __('Unlock writing, Autopilot and answer simulations', 'ai-rank-suite'), action: __('Connect', 'ai-rank-suite'), href: '/ai', tone: 'warning' }]
			: []),
		...(todo.filter((c) => c.status === 'fail').length > 0
			? [{ label: __('Fix failing site checks', 'ai-rank-suite'), detail: sprintf(_n('%d check is failing now', '%d checks are failing now', todo.filter((c) => c.status === 'fail').length, 'ai-rank-suite'), todo.filter((c) => c.status === 'fail').length), action: __('Open audit', 'ai-rank-suite'), href: '/audit', tone: 'bad' }]
			: []),
		...(links.broken > 0
			? [{ label: __('Repair broken links', 'ai-rank-suite'), detail: sprintf(_n('%d broken link found', '%d broken links found', links.broken, 'ai-rank-suite'), links.broken), action: __('Open checker', 'ai-rank-suite'), href: '/links/checker', tone: 'warning' }]
			: []),
		...(content?.missing_desc > 0
			? [{ label: __('Write missing descriptions', 'ai-rank-suite'), detail: sprintf(_n('%d published page has none', '%d published pages have none', content.missing_desc, 'ai-rank-suite'), content.missing_desc), action: __('Open Autopilot', 'ai-rank-suite'), href: '/autopilot', tone: 'warning' }]
			: []),
		...(content?.unanalyzed > 0
			? [{ label: __('Analyse the remaining content', 'ai-rank-suite'), detail: sprintf(__('%d pages have no score yet', 'ai-rank-suite'), content.unanalyzed), action: __('Open analysis', 'ai-rank-suite'), href: '/analyzer', tone: 'neutral' }]
			: []),
		...(setup && !setup.google
			? [{ label: __('Connect Search Console', 'ai-rank-suite'), detail: __('Add real clicks, impressions and positions', 'ai-rank-suite'), action: __('Connect', 'ai-rank-suite'), href: '/search-console', tone: 'neutral' }]
			: []),
		...(setup && setup.visibility_prompts === 0
			? [{ label: __('Add answer-test prompts', 'ai-rank-suite'), detail: __('Start a repeatable AI mention baseline', 'ai-rank-suite'), action: __('Add prompts', 'ai-rank-suite'), href: '/ai-visibility', tone: 'neutral' }]
			: []),
	].slice(0, 3);

	return (
		<div className="ars-page">
			{/* The one sentence the whole Pro tier is bought for, on the one
			    screen every user opens. The sales pitch is FREE EDITION ONLY —
			    advertising an upgrade to a customer who paid is how one-star
			    reviews start. An unlicensed Pro build gets the other card: it
			    already holds the full product and only needs its licence
			    connected, so asking it to "upgrade" reads as a double charge. */}
			{!isPro() && (isProBuild() ? (
				<div className="ars-upsell">
					<div className="min-w-0">
						<div className="ars-upsell-kicker">{__('AI Rank Suite Pro', 'ai-rank-suite')}</div>
						<div className="ars-upsell-title">{__('Your Pro license is not connected yet.', 'ai-rank-suite')}</div>
						<p className="ars-upsell-text">{__('This is the full Pro build — every feature is installed. Connect the license from your purchase email and everything unlocks. No re-purchase, no extra cost.', 'ai-rank-suite')}</p>
					</div>
					<div className="ars-upsell-cta">
						<a className="ars-btn ars-btn-primary" href={licenseUrl()}>{__('Activate license', 'ai-rank-suite')}</a>
						<span className="ars-upsell-price">{__('The key is in your purchase email.', 'ai-rank-suite')}</span>
					</div>
				</div>
			) : (
				<div className="ars-upsell">
					<div className="min-w-0">
						<div className="ars-upsell-kicker">{__('AI Rank Suite Pro', 'ai-rank-suite')}</div>
						<div className="ars-upsell-title">{__('Does ChatGPT recommend you — or your competitor?', 'ai-rank-suite')}</div>
						<p className="ars-upsell-text">{__('Pro asks the assistants the 25 questions your customers ask, tells you who was named instead of you, and writes what is missing — on your own AI key. No credits, no seats.', 'ai-rank-suite')}</p>
					</div>
					<div className="ars-upsell-cta">
						<a className="ars-btn ars-btn-primary" href={licenseUrl()}>{__('Upgrade to Pro', 'ai-rank-suite')}</a>
						<span className="ars-upsell-price">{__('$49/year · 14-day money back', 'ai-rank-suite')}</span>
					</div>
				</div>
			))}
			{error && <Notice tone="serious">{error}</Notice>}

			{/*
			 * The masthead: the site's overall score, the one sentence that says
			 * what it means, and the counters behind it.
			 *
			 * This was a caption and two chips — the first thing on the first
			 * screen said nothing about the site. Every number here appears
			 * again further down, so the headline can be checked rather than
			 * believed, and each counter is a link to the screen that owns it.
			 */}
			<div className="ars-hero">
				<div className="ars-hero-ring">
					<ScoreRing value={overall ?? null} size={132} label={__('AI search', 'ai-rank-suite')} onLight />
					{/*
					  * What the number is, in one line.
					  *
					  * A score out of a hundred with nothing beside it invites
					  * exactly the wrong question ("is 44 good?") and no way to
					  * answer it. This says how it is built and where the two
					  * thresholds sit, which is the difference between a number
					  * somebody argues with and a number they ignore.
					  */}
					<p className="t-caption ars-hero-ring-note">
						{__('The share of the plugin’s checks this site passes, weighted by area; a half-pass counts half. Under 40 is critical, 70 and over is good.', 'ai-rank-suite')}
					</p>
				</div>

				<div className="ars-hero-body">
					<span className={`ars-hero-badge is-${verdict.tone}`}>
						{verdict.tone === 'good'
							? __('Healthy', 'ai-rank-suite')
							: verdict.tone === 'bad'
								? __('Needs attention', 'ai-rank-suite')
								: verdict.tone === 'warning'
									? __('Room to improve', 'ai-rank-suite')
									: __('Getting started', 'ai-rank-suite')}
					</span>
					<h2 className="ars-hero-title">{verdict.text}</h2>
					<p className="ars-hero-why">{verdict.why}</p>

					<div className="ars-hero-actions">
						<button type="button" className="ars-btn ars-btn-primary" onClick={() => go(verdict.href)}>
							{verdict.action}
						</button>
						<button type="button" className="ars-btn ars-btn-ghost" onClick={recalc} disabled={busy}>
							{busy ? __('Recalculating…', 'ai-rank-suite') : __('Recalculate', 'ai-rank-suite')}
						</button>
					</div>
					{/*
					 * A bar while it works, with no percentage on it.
					 *
					 * /score/recalculate is one request that answers when it is
					 * done — there is no progress to report, and inventing a
					 * percentage would be a lie told with a straight face. An
					 * indeterminate bar says the true thing: something is
					 * running, and it has not finished.
					 */}
					{busy && (
						<div className="ars-progress-track is-indeterminate" role="progressbar" aria-label={__('Recalculating…', 'ai-rank-suite')}>
							<div className="ars-progress-fill" />
						</div>
					)}
				</div>

				<div className="ars-hero-stats">
					{[
						{ label: __('Pages scored', 'ai-rank-suite'), value: content ? `${(content.analyzed ?? 0).toLocaleString()} / ${(content.pages ?? 0).toLocaleString()}` : '–', href: '/content', help: __('Pages analysed at least once, out of the published pages on this site.', 'ai-rank-suite') },
						/*
						 * The audit's findings, not the site checks.
						 *
						 * This counted `todo` — the handful of foundation
						 * checks — while wearing the audit's label and linking
						 * to the audit page. On a live site it read "0 open
						 * findings" beside an audit reporting 43 warnings and
						 * 292 notices: the first number a person looks at gave
						 * an all-clear that was wrong by 335.
						 */
						/*
						 * Rules, not findings.
						 *
						 * This read "17,733 open findings" on a site of 2,538
						 * pages. Nobody opens a list of seventeen thousand, so
						 * the number was a wall rather than a measurement. The
						 * same run came from twelve rules, and a rule is the
						 * unit of work: one fix to a template clears every page
						 * it touches. The finding count is still here, as the
						 * explanation of the rule count rather than instead of
						 * it.
						 */
						{
							label: __('Rules failing', 'ai-rank-suite'),
							value: k.audit?.rules_total
								? `${(k.audit.rules ?? 0).toLocaleString()} / ${k.audit.rules_total.toLocaleString()}`
								: (k.audit?.rules ?? 0).toLocaleString(),
							href: '/audit',
							tone: k.audit?.rules_critical ? 'bad' : k.audit?.rules ? 'warn' : 'good',
							help: k.audit?.total
								? sprintf(
										/* translators: 1: number of findings, 2: number of pages. */
										__('Audit rules that found something on the last run. They account for %1$s findings across %2$s pages; fixing a rule clears every page it touches.', 'ai-rank-suite'),
										(k.audit.total ?? 0).toLocaleString(),
										(k.audit.pages ?? 0).toLocaleString()
									)
								: __('Audit rules that found something on the last run. Nothing is open.', 'ai-rank-suite'),
						},
						{ label: __('Broken links', 'ai-rank-suite'), value: (links.broken ?? 0).toLocaleString(), href: '/links/checker', tone: links.broken ? 'warn' : 'good', help: __('Links checked from your own pages that answered with an error the last time they were fetched.', 'ai-rank-suite') },
						{
							label: __('AI engine', 'ai-rank-suite'),
							value: setup === null ? '–' : setup.ai ? __('Connected', 'ai-rank-suite') : __('Not connected', 'ai-rank-suite'),
							href: '/ai',
							tone: setup === null ? undefined : setup.ai ? 'good' : 'warn',
							help: __('Whether a model key is stored. Without one, nothing here writes, rewrites or simulates an answer.', 'ai-rank-suite'),
						},
					].map((s) => (
						<button key={s.label} type="button" className="ars-hero-stat" onClick={() => go(s.href)} title={s.help}>
							<span className={`ars-hero-stat-v${s.tone ? ' is-' + s.tone : ''}`}>{s.value}</span>
							<span className="ars-hero-stat-l">{s.label}</span>
							<span className="ars-hero-stat-h">{s.help}</span>
						</button>
					))}
				</div>
			</div>




			{/*
			 * What to do, before what is wrong.
			 *
			 * The three actions used to sit below the AI card, which meant the
			 * first thing under the verdict was a status report. Somebody who
			 * opens this screen has a few minutes: the shortest path from the
			 * headline to a fix is the list of fixes, so it comes first.
			 */}
			{priorities.length > 0 && (
				<Card title={__('Your next 3 actions', 'ai-rank-suite')} subtitle={__('Ordered by impact — finish one, then come back for the next', 'ai-rank-suite')}>
					<div className="grid gap-2">
						{priorities.map((item, index) => (
							<div
								key={item.label}
								className="flex items-center gap-3 ars-r-md px-3 py-2.5"
								style={{ background: 'var(--ars-surface-2)', border: '1px solid var(--ars-border)' }}
							>
								<span className={`ars-priority-number is-${item.tone}`} aria-hidden>{index + 1}</span>
								<div className="min-w-0 flex-1">
									<div className="font-semibold text-[13px]">{item.label}</div>
									<div className="t-caption mt-0.5">{item.detail}</div>
								</div>
								<Button variant={index === 0 ? 'primary' : 'ghost'} onClick={() => go(item.href)}>
									{item.action}
								</Button>
							</div>
						))}
					</div>
				</Card>
			)}

			{/*
			 * What the AI has actually been doing, with what it cost. Drawn
			 * only once an engine is connected; until then there is nothing to
			 * report and the verdict above has already asked for a key.
			 */}
			<AiAtWork />

			{/*
			 * A shop gets a band of its own, above the histories. The readiness
			 * number is the catalogue's own verdict, and the three gaps beside
			 * it are the three lists a merchant opens first.
			 */}
			{commerce.active && typeof commerce.readiness === 'number' && (
				<Card
					title={__('Shop', 'ai-rank-suite')}
					subtitle={sprintf(
						/* translators: %s: number of products. */
						_n('%s published product', '%s published products', commerce.products || 0, 'ai-rank-suite'),
						(commerce.products || 0).toLocaleString()
					)}
					action={<Button variant="ghost" onClick={() => go('/shop')}>{__('Open the shop screen', 'ai-rank-suite')}</Button>}
				>
					<div className="ars-hero-stats">
						{[
							{ label: __('Shop readiness', 'ai-rank-suite'), value: `${commerce.readiness} / 100`, tone: commerce.readiness >= 80 ? 'good' : commerce.readiness >= 50 ? 'warn' : 'bad', help: __('Product data, product text, sellable pages and product schema, weighed together.', 'ai-rank-suite') },
							{ label: __('Without image', 'ai-rank-suite'), value: (commerce.counts?.no_image ?? 0).toLocaleString(), tone: commerce.counts?.no_image ? 'warn' : 'good', help: __('A product without an image is not shown as a product by Google.', 'ai-rank-suite') },
							{ label: __('Thin description', 'ai-rank-suite'), value: (commerce.counts?.thin ?? 0).toLocaleString(), tone: commerce.counts?.thin ? 'warn' : 'good', help: __('Under 200 characters. An AI answer has nothing to quote.', 'ai-rank-suite') },
							{ label: __('Not scored yet', 'ai-rank-suite'), value: (commerce.unscored ?? 0).toLocaleString(), tone: commerce.unscored ? 'warn' : 'good', help: __('Products the analyser has not read. Analyse them under Content.', 'ai-rank-suite') },
						].map((s) => (
							<button key={s.label} type="button" className="ars-hero-stat" onClick={() => go('/shop')} title={s.help}>
								<span className={`ars-hero-stat-v is-${s.tone}`}>{s.value}</span>
								<span className="ars-hero-stat-l">{s.label}</span>
								<span className="ars-hero-stat-h">{s.help}</span>
							</button>
						))}
					</div>
				</Card>
			)}

			{/* Two histories: score and findings. Coverage over time moved to
			    Insights with the metric inventory (1.0 Phase 2). */}
			<DashboardTrends charts={['score', 'findings']} revision={trendRevision} />

			{/* ---------- Launchpad: every cell a live number, every cell a destination ----------
			 *
			 * Five bands of four generous tiles became four bordered grids. The
			 * numbers did not change; the space between them did. Twenty metrics
			 * in five headed blocks cost a screenful of chrome, and the sentence
			 * under every tile was read once and scrolled past forever after —
			 * it lives in the `?` hover now, where explanation belongs. Density
			 * is the feature: the reason to open this screen is to spot the one
			 * number that moved, and that only works when they share a glance.
			 *
			 * Technical health and Store merged into one grid of six when
			 * WooCommerce is active: both were short, and neither is what
			 * somebody opens the dashboard for. Redirected links became a count
			 * inside the broken-links cell, and "Structured data: Live" — a
			 * constant, not a measurement — folded into the Products cell.
			 */}



			{content && (content.areas || []).length > 0 && (
				<Card
					title={__('Where this site is weak', 'ai-rank-suite')}
					subtitle={
						content.unanalyzed > 0
							? sprintf(
									/* translators: 1: analyzed pages, 2: pages not analyzed. */
									__('Averaged across all %1$s analyzed pages — %2$s not analyzed yet', 'ai-rank-suite'),
									content.analyzed.toLocaleString(),
									content.unanalyzed.toLocaleString()
								)
							: sprintf(__('Averaged across all %s pages', 'ai-rank-suite'), content.analyzed.toLocaleString())
					}
					action={
						<button type="button" className="ars-linkbtn" onClick={() => go('/analyzer')}>{__('Open the full analysis →', 'ai-rank-suite')}</button>
					}
					flush
				>
					<AreaMap areas={content.areas} onDrill={(a) => setArea(a === area ? null : a)} active={area} analyzed={content.analyzed} />
					{area && <AreaDrill area={area} onOpen={(id) => go(`/content/${id}`)} />}
				</Card>
			)}

			{content && content.needs_attention.length > 0 && (
				<Card
					title={__('Pages needing attention', 'ai-rank-suite')}
					subtitle={__('Lowest combined GEO + SEO across the whole site — ranked in the database, not from a sample', 'ai-rank-suite')}
					action={
						<button
							type="button"
							className="ars-linkbtn"
							onClick={() => go('/content?filter=weak_geo')}
						>{__('See all weak pages →', 'ai-rank-suite')}</button>
					}
					flush
				>
					<div className="ars-table-wrap">
						<table className="ars-table">
							<thead>
								<tr>
									<th>{__('Page', 'ai-rank-suite')}</th>
									<th className="is-mid" style={{ width: 90 }}>{__('Type', 'ai-rank-suite')}</th>
									<th className="is-num is-narrow">{__('GEO', 'ai-rank-suite')}</th>
									<th className="is-num is-narrow">{__('SEO', 'ai-rank-suite')}</th>
								</tr>
							</thead>
							<tbody>
								{content.needs_attention.slice(0, 5).map((p) => (
									<tr key={p.id} style={{ cursor: 'pointer' }} onClick={() => go(`/content/${p.id}`)}>
										<td className="font-medium truncate">{p.title}</td>
										<td className="is-mid t-caption">{p.type}</td>
										<td className="is-num is-narrow">
											<ScorePill value={p.geo_score} />
										</td>
										<td className="is-num is-narrow">
											<ScorePill value={p.seo_score} />
										</td>
									</tr>
								))}
							</tbody>
						</table>
					</div>
				</Card>
			)}

			{/* The full check list is reference, not news — folded, so the
			    screen ends on work rather than on an inventory. Module
			    management lives under Settings (1.0 Phase 2). */}


			{/* Compact system status: connections, coexistence and the
			    shortcuts that used to crowd the masthead (1.0 Phase 2). */}
			<Card title={__('System status', 'ai-rank-suite')} subtitle={__('Connections and coexistence at a glance', 'ai-rank-suite')}>
				<div className="grid gap-1.5">
					{[
						{ label: __('AI engine', 'ai-rank-suite'), ok: !!setup?.ai, text: setup === null ? '–' : setup.ai ? __('Connected', 'ai-rank-suite') : __('Not connected', 'ai-rank-suite'), action: setup?.ai ? __('Open', 'ai-rank-suite') : __('Connect', 'ai-rank-suite'), href: '/ai' },
						{ label: __('Search Console', 'ai-rank-suite'), ok: !!setup?.google, text: setup === null ? '–' : setup.google ? __('Connected', 'ai-rank-suite') : __('Not connected', 'ai-rank-suite'), action: setup?.google ? __('Open', 'ai-rank-suite') : __('Connect', 'ai-rank-suite'), href: '/search-console' },
						...(setup?.other_seo_plugin
							? [{ label: __('Other SEO plugin', 'ai-rank-suite'), ok: false, text: sprintf(/* translators: %s: plugin name. */ __('%s is still active — import its settings, then deactivate it so only one plugin controls SEO output.', 'ai-rank-suite'), setup.other_seo_plugin), action: __('Open import', 'ai-rank-suite'), href: '/import' }]
							: []),
						{ label: __('Modules', 'ai-rank-suite'), ok: true, text: __('Enable or disable every module under Settings.', 'ai-rank-suite'), action: __('Manage', 'ai-rank-suite'), href: '/settings' },
					].map((r) => (
						<div key={r.label} className="flex items-center gap-3 ars-r-md px-3 py-2" style={{ background: 'var(--ars-surface-2)' }}>
							<span aria-hidden style={{ width: 8, height: 8, borderRadius: 999, flex: 'none', background: r.ok ? 'var(--ars-good)' : 'var(--ars-warning)' }} />
							<div className="min-w-0 flex-1">
								<span className="font-medium text-[13px]">{r.label}</span>
								<span className="t-caption" style={{ marginInlineStart: 8 }}>{r.text}</span>
							</div>
							<button type="button" className="ars-linkbtn" onClick={() => go(r.href)}>{r.action}</button>
						</div>
					))}
					<p className="m-0 mt-1 t-caption">
						<button type="button" className="ars-linkbtn" onClick={() => go('/wizard')}>{__('Setup wizard', 'ai-rank-suite')}</button>
						{' · '}
						<button type="button" className="ars-linkbtn" onClick={() => { window.location.hash = '/ai-wizard'; }}>{__('AI setup', 'ai-rank-suite')}</button>
						{' · '}
						<button type="button" className="ars-linkbtn" onClick={() => go('/ai')}>{__('Let AI do — open the catalogue', 'ai-rank-suite')}</button>
					</p>
				</div>
			</Card>

			<Panel
				title={__('All checks', 'ai-rank-suite')}
				note={sprintf(__('%1$d of %2$d passing', 'ai-rank-suite'), checks.filter((c) => c.status === 'pass').length, checks.length)}
			>
				<div className="ars-table-wrap"><table className="ars-table">
					<thead>
						<tr>
							<th>{__('Check', 'ai-rank-suite')}</th>
							<th className="is-mid">{__('Dimension', 'ai-rank-suite')}</th>
							<th className="is-action">{__('Status', 'ai-rank-suite')}</th>
						</tr>
					</thead>
					<tbody>
						{checks.map((c) => (
							<tr key={c.id}>
								<td>{c.label}</td>
								<td className="capitalize is-mid" style={{ color: 'var(--ars-ink-2)' }}>
									{c.dimension}
								</td>
								<td className="is-action">
									<StatusBadge status={c.status} />
								</td>
							</tr>
						))}
					</tbody>
				</table></div>
			</Panel>
		</div>
	);
}

/**
 * The health map, at any size.
 *
 * A row per page stops working somewhere around fifty pages and is actively
 * misleading at a thousand: you are looking at a sample and cannot tell. So
 * the map is the seven areas across the whole site — one row each, with the
 * average, a distribution bar, and how many pages sit in each band. A cold row
 * here is a site-wide habit, which is the only thing worth acting on at scale,
 * and clicking it names the specific pages.
 */
/**
 * Where this site is weak, on the shared instrument.
 *
 * This block was the pattern everything else was rebuilt around, and it was
 * still the one place using its own markup — it looked like a ScoreRow without
 * being one, which is exactly the arrangement where the two quietly drift apart
 * on the next change. Now it is the component, so the dashboard, the audit and
 * AI Visibility cannot disagree about what a weakness row looks like.
 */
function AreaMap({ areas, onDrill, active, analyzed = 0 }) {
	return (
		<div className="px-4 py-2">
			{/*
			 * The bar is three counted parts, and until now nothing on the
			 * screen said which part was which — seven rows of red, amber and
			 * green with the explanation hidden in a tooltip a phone cannot
			 * show. One legend, once, above all of them: the same fix the
			 * T · D · K letters needed, for the same reason.
			 */}
			<div className="ars-legend">
				<span className="ars-legend-label">{__('Each bar is every page in this area:', 'ai-rank-suite')}</span>
				<span className="ars-legend-key"><i style={{ background: 'var(--ars-c-red-ink)' }} />{__('critical', 'ai-rank-suite')}</span>
				<span className="ars-legend-key"><i style={{ background: 'var(--ars-accent)' }} />{__('weak', 'ai-rank-suite')}</span>
				<span className="ars-legend-key"><i style={{ background: 'var(--ars-c-green-ink)' }} />{__('good', 'ai-rank-suite')}</span>
			</div>
			{areas.map((a) => {
				const has = a.pages > 0 && a.average !== null;
				return (
					<ScoreRow
						key={a.key}
						score={has ? a.average : undefined}
						label={a.label}
						sub={a.hint}
						segments={
							has
								? [
										{ label: __('Critical', 'ai-rank-suite'), value: a.critical || 0, color: 'var(--ars-c-red-ink)' },
										{ label: __('Weak', 'ai-rank-suite'), value: a.weak || 0, color: 'var(--ars-accent)' },
										{ label: __('Good', 'ai-rank-suite'), value: a.good || 0, color: 'var(--ars-c-green-ink)' },
									]
								: [{ label: __('Not analysed', 'ai-rank-suite'), value: 1, color: 'var(--ars-ink-3)' }]
						}
						reach={
							has
								? sprintf(
										/* translators: 1: pages needing work, 2: pages in the area. */
										__('%1$s of %2$s pages need work here', 'ai-rank-suite'),
										(a.critical + a.weak).toLocaleString(),
										a.pages.toLocaleString()
									)
								: __('not analysed yet', 'ai-rank-suite')
						}
						/*
						 * Rows do not all count the same pages: search terms
						 * only exist on pages that have one. Without a word
						 * about it, "138 of 151" beside "157 of 158" reads
						 * like one of the two numbers is wrong.
						 */
						note={
							has && analyzed > 0 && a.pages < analyzed
								? sprintf(
										/* translators: %s: pages without this signal. */
										__('%s pages are not counted here because this does not apply to them yet', 'ai-rank-suite'),
										(analyzed - a.pages).toLocaleString()
									)
								: undefined
						}
						onClick={has ? () => onDrill(a.key) : undefined}
					/>
				);
			})}
			<p className="m-0 px-1 py-2 t-caption">{__('Click a row to see the pages behind it.', 'ai-rank-suite')}</p>
		</div>
	);
}


/** The pages behind one cold area. */
function AreaDrill({ area, onOpen }) {
	const [rows, setRows] = useState(null);
	useEffect(() => {
		setRows(null);
		api
			.get(`/score/area/${area}`)
			.then((r) => setRows(r.rows || []))
			.catch(() => setRows([]));
	}, [area]);

	if (!rows) return <p className="m-0 px-4 pb-4 t-caption">{__('Loading the worst pages…', 'ai-rank-suite')}</p>;
	if (rows.length === 0) return <p className="m-0 px-4 pb-4 t-caption">{__('No pages scored in this area yet.', 'ai-rank-suite')}</p>;

	return (
		<div className="ars-table-wrap" style={{ borderTop: '1px solid var(--ars-border)' }}>
			<table className="ars-table">
				<thead>
					<tr>
						<th>{__('The 20 worst pages in this area', 'ai-rank-suite')}</th>
						<th className="is-mid" style={{ width: 90 }}>{__('Type', 'ai-rank-suite')}</th>
						<th className="is-num is-narrow">{__('Score', 'ai-rank-suite')}</th>
					</tr>
				</thead>
				<tbody>
					{rows.map((r) => (
						<tr key={r.id} style={{ cursor: 'pointer' }} onClick={() => onOpen(r.id)}>
							<td className="font-medium truncate">{r.title || `#${r.id}`}</td>
							<td className="is-mid t-caption">{r.type}</td>
							<td className="is-num is-narrow">
								<ScorePill value={r.score} />
							</td>
						</tr>
					))}
				</tbody>
			</table>
		</div>
	);
}
