/**
 * Dashboard Page Component
 *
 * Card-based overview: KPI row, usage overview chart, top used modes,
 * accessibility analyzer, quick actions and a documentation banner.
 *
 * @package Everyone_Accessibility_Suite
 */

import { useState, useEffect, useCallback, useRef } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import {
	getAccessibilitySettings,
	updateAccessibilitySettings,
	getModules,
	getAnalyticsSummary,
	getAnalyticsSettings,
	getAnalyticsStats,
	getAnalyzerFrontPage,
} from '../services/settings-api';
import { PageHeader, Loading } from '../components/ui';
import DateRangePicker, { formatRangeLabel } from '../components/DateRangePicker';

const DAY = 86400;

// Mirrors SESSION_KEY in assets/accessibility/js/live-scanner.js: the list of
// URLs that scanner has already covered in this browser session. The dashboard
// clears it to force a rescan - keep the two in step.
const SCANNER_SESSION_KEY = 'evas_a11y_scanned';

/**
 * Default reporting window: the last 7 days *inclusive* - today plus the six
 * days before it, so the picker reads e.g. "Aug 21 - Aug 27" rather than
 * spanning eight calendar days.
 */
const defaultRange = () => {
	const now = new Date();
	const startOfToday = Math.floor(new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() / 1000);
	return { from: startOfToday - 6 * DAY, to: Math.floor(Date.now() / 1000) };
};

/* -------------------------------------------------------------------------- */
/*  Small shared pieces                                                        */
/* -------------------------------------------------------------------------- */

/**
 * Growth badge shown on the KPI cards. Renders nothing when there is no
 * baseline to compare against (delta === null/undefined).
 */
const DeltaBadge = ({ value, suffix = '%' }) => {
	if (value === null || value === undefined || Number.isNaN(value)) {
		return null;
	}

	const up = value >= 0;
	const magnitude = Math.abs(value);
	// A near-empty baseline produces percentages in the thousands, which say
	// nothing useful and push the badge out of the card. Past 999% the exact
	// figure stops mattering - it grew.
	const shown = suffix === '%' && magnitude > 999 ? '>999' : magnitude;

	return (
		<span className={`evas-delta-badge ${up ? 'is-up' : 'is-down'}`}>
			{up ? '↑' : '↓'} {shown}{suffix}
		</span>
	);
};

const KpiCard = ({ icon, iconTone = 'primary', label, value, valueClass = '', badge, hint, children }) => (
	<div className="evas-kpi-card">
		<div className="evas-kpi-card__head">
			<span className={`dashicons dashicons-${icon} is-${iconTone}`}></span>
			<span className="evas-kpi-card__label">{label}</span>
		</div>
		<div className="evas-kpi-card__value-row">
			<span className={`evas-kpi-card__value ${valueClass}`}>{value}</span>
			{badge}
		</div>
		{hint && <span className="evas-kpi-card__hint">{hint}</span>}
		{children}
	</div>
);

/**
 * Toggle SVGs that ship with the panel. Kept as an explicit list so we only
 * request art that exists (a missing file would log a 404).
 */
const MODE_ICONS = new Set([
	'align-center', 'align-left', 'align-right', 'black-cursor', 'cognitive-reading',
	'dark-contrast', 'dyslexia-font', 'hide-emoji', 'hide-images', 'high-contrast',
	'high-saturation', 'highlight-focus', 'highlight-hover', 'highlight-links',
	'highlight-titles', 'keyboard-navigation', 'light-contrast', 'low-saturation',
	'monochrome', 'mute-sounds', 'readable-font', 'reading-guide', 'reading-mask',
	'stop-animations', 'text-magnifier', 'text-to-speech', 'virtual-keyboard',
	'voice-navigation', 'white-cursor',
]);

const MODE_ICON_ALIASES = {
	big_black_cursor: 'black-cursor',
	big_white_cursor: 'white-cursor',
};

/**
 * Dashicon stand-ins for features the panel ships no toggle SVG for, so the
 * list doesn't fall back to a row of identical generic icons.
 */
const MODE_DASHICONS = {
	text_colors: 'editor-textcolor',
	title_colors: 'editor-textcolor',
	background_colors: 'art',
	useful_links: 'admin-links',
	content_scaling: 'editor-expand',
	font_sizing: 'editor-textcolor',
	line_height: 'editor-insertmore',
	letter_spacing: 'editor-code',
};

/**
 * Icon for a "top used mode" row: the panel's own toggle SVG when one exists,
 * otherwise a generic dashicon.
 */
const ModeIcon = ({ slug }) => {
	const pluginUrl = window.evasAdmin?.pluginUrl || '';
	const key = MODE_ICON_ALIASES[slug] || String(slug || '').replace(/_/g, '-');

	return (
		<span className="evas-mode-row__icon">
			{MODE_ICONS.has(key) ? (
				<img src={`${pluginUrl}assets/accessibility/images/toggles/${key}.svg`} alt="" />
			) : (
				<span className={`dashicons dashicons-${MODE_DASHICONS[slug] || 'admin-generic'}`}></span>
			)}
		</span>
	);
};

/* -------------------------------------------------------------------------- */
/*  Disabled / empty notices (kept from the previous dashboard)                */
/* -------------------------------------------------------------------------- */

const AnalyticsDisabledNotice = () => (
	<div className="evas-analytics-disabled">
		<span className="dashicons dashicons-chart-bar"></span>
		<h4 className="evas-analytics-disabled__title">{__( 'Usage tracking is off', 'everyone-accessibility-suite' )}</h4>
		<p className="evas-analytics-disabled__text">
			{__( 'Turn on usage tracking to collect sessions, panel opens and feature statistics. No data is collected while it is disabled.', 'everyone-accessibility-suite' )}
		</p>
		<a href="#/usage-analytics" className="button button-primary evas-analytics-disabled__action">
			{__( 'Enable Usage Analytics', 'everyone-accessibility-suite' )}
		</a>
	</div>
);

const AnalyzerDisabledNotice = () => (
	<div className="evas-analytics-disabled">
		<span className="dashicons dashicons-search"></span>
		<h4 className="evas-analytics-disabled__title">{__( 'Accessibility Analyzer is off', 'everyone-accessibility-suite' )}</h4>
		<p className="evas-analytics-disabled__text">
			{__( 'Turn on the Accessibility Analyzer module to scan your homepage for WCAG issues.', 'everyone-accessibility-suite' )}
		</p>
		<a href="#/analyzer" className="button button-primary evas-analytics-disabled__action">
			{__( 'Enable Accessibility Analyzer', 'everyone-accessibility-suite' )}
		</a>
	</div>
);

const AnalyzerPendingNotice = ({ homeUrl }) => (
	<div className="evas-analytics-disabled">
		<span className="dashicons dashicons-clock"></span>
		<h4 className="evas-analytics-disabled__title">{__( 'No scan yet', 'everyone-accessibility-suite' )}</h4>
		<p className="evas-analytics-disabled__text">
			{__( 'The homepage is scanned automatically (using axe-core) the next time you visit it while logged in as an administrator, or run a scan now.', 'everyone-accessibility-suite' )}
		</p>
		<a href={homeUrl} target="_blank" rel="noopener noreferrer" className="button button-primary evas-analytics-disabled__action">
			{__( 'Open Homepage', 'everyone-accessibility-suite' )}
		</a>
	</div>
);

/* -------------------------------------------------------------------------- */
/*  Usage Overview card                                                        */
/* -------------------------------------------------------------------------- */

/**
 * One tile under the usage chart: small captioned label on top, big number
 * below. `highlight` tints the primary metric (panel opens).
 */
const OverviewStat = ({ icon, label, value, highlight = false }) => (
	<div className={`evas-overview-stat ${highlight ? 'is-highlight' : ''}`}>
		<span className="evas-overview-stat__label">
			<span className={`dashicons dashicons-${icon}`}></span>
			{label}
		</span>
		<span className="evas-overview-stat__value">{value}</span>
	</div>
);

const UsageOverviewCard = ({ enabled, loading, summary, hourlyData, granularity, onGranularityChange }) => {
	const canvasRef = useRef(null);

	const renderChart = useCallback(() => {
		const canvas = canvasRef.current;
		if (!canvas) return;

		const ctx = canvas.getContext('2d');
		// Floor the box to whole CSS pixels and pin that size before scaling by
		// the device pixel ratio. A card is rarely a round width, and
		// `fractional * dpr` truncates on assignment to canvas.width, mapping
		// the backing store to the box at very slightly less than dpr - enough
		// to put every coordinate a fraction of a pixel off and render the whole
		// chart soft.
		const dpr = window.devicePixelRatio || 1;
		const rect = canvas.getBoundingClientRect();
		const width = Math.floor(rect.width);
		const height = Math.floor(rect.height);

		if (width <= 0 || height <= 0) return;

		canvas.width = Math.round(width * dpr);
		canvas.height = Math.round(height * dpr);
		canvas.style.width = `${width}px`;
		canvas.style.height = `${height}px`;
		ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
		ctx.clearRect(0, 0, width, height);

		// The API returns 25 buckets: index 0 is the oldest, the last one is
		// the current (partial) bucket - "Now" on the axis. Keeping all 25
		// means the right-hand edge gets a label at i = 24.
		const open = (hourlyData?.hourlyOpen || []).slice(-25);
		const loaded = (hourlyData?.hourlyLoadSaved || []).slice(-25);

		if (open.length === 0) {
			ctx.fillStyle = '#9ca3af';
			ctx.font = '13px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
			ctx.textAlign = 'center';
			ctx.fillText(__( 'No data available', 'everyone-accessibility-suite' ), width / 2, height / 2);
			return;
		}

		const maxValue = Math.max(
			...open.map((v) => parseInt(v, 10) || 0),
			...loaded.map((v) => parseInt(v, 10) || 0),
			1
		);

		// Inset on all four sides: the y-axis labels live in `left`, the last
		// bar keeps clear of the card edge via `right`, and `bottom` is the
		// strip the hour labels sit in.
		const padding = { top: 12, right: 20, bottom: 28, left: 42 };
		const chartWidth = width - padding.left - padding.right;
		const chartHeight = height - padding.top - padding.bottom;
		const barCount = open.length;
		const slot = chartWidth / barCount;
		const barWidth = Math.max(slot / 2 - 1, 1);

		ctx.strokeStyle = '#eef1f4';
		ctx.lineWidth = 1;
		ctx.fillStyle = '#94a3b8';
		ctx.font = '10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
		ctx.textAlign = 'right';
		// The +0.5 centres a 1px stroke inside one pixel row - on a whole
		// coordinate it straddles two and renders as two grey half-lines.
		for (let i = 0; i <= 4; i++) {
			const y = Math.round(padding.top + (chartHeight * i) / 4) + 0.5;
			ctx.beginPath();
			ctx.moveTo(padding.left, y);
			ctx.lineTo(width - padding.right, y);
			ctx.stroke();
			const labelValue = Math.round((maxValue * (4 - i)) / 4);
			ctx.fillText(String(labelValue), padding.left - 6, y + 3);
		}

		// Bar edges snapped to whole pixels - a rect on fractional coordinates
		// is anti-aliased into a soft edge even at the right resolution.
		const baseline = Math.round(padding.top + chartHeight);
		const barPx = Math.max(Math.floor(barWidth), 1);

		for (let i = 0; i < barCount; i++) {
			const x = Math.round(padding.left + i * slot);

			const loadedValue = parseInt(loaded[i], 10) || 0;
			const loadedHeight = Math.round((loadedValue / maxValue) * chartHeight);
			ctx.fillStyle = 'rgba(148, 163, 184, 0.55)';
			ctx.fillRect(x + 1, baseline - loadedHeight, barPx, loadedHeight);

			const openValue = parseInt(open[i], 10) || 0;
			const openHeight = Math.round((openValue / maxValue) * chartHeight);
			ctx.fillStyle = 'rgba(37, 99, 235, 0.9)';
			ctx.fillRect(x + barPx + 1, baseline - openHeight, barPx, openHeight);
		}

		ctx.fillStyle = '#94a3b8';
		ctx.font = '10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
		ctx.textAlign = 'center';
		const unit = granularity === 'day' ? 'd' : 'h';
		for (let i = 0; i < barCount; i += 6) {
			const x = padding.left + i * slot + slot / 2;
			const back = barCount - 1 - i;
			ctx.fillText(back === 0 ? __( 'Now', 'everyone-accessibility-suite' ) : `${back}${unit}`, x, height - 4);
		}
	}, [hourlyData, granularity]);

	useEffect(() => {
		renderChart();
	}, [renderChart]);

	useEffect(() => {
		const handleResize = () => renderChart();
		window.addEventListener('resize', handleResize);
		return () => window.removeEventListener('resize', handleResize);
	}, [renderChart]);

	return (
		<section className="evas-card evas-overview-card">
			<header className="evas-card__header evas-card__header--accent">
				<h3>
					<span className="dashicons dashicons-chart-bar"></span>
					{__( 'Usage Overview', 'everyone-accessibility-suite' )}
				</h3>
				<select
					className="evas-card__header-select"
					value={granularity}
					onChange={(e) => onGranularityChange(e.target.value)}
					disabled={!enabled}
				>
					<option value="hour">{__( 'By Hour', 'everyone-accessibility-suite' )}</option>
					<option value="day">{__( 'By Day', 'everyone-accessibility-suite' )}</option>
				</select>
			</header>

			{!enabled ? (
				<AnalyticsDisabledNotice />
			) : loading ? (
				<Loading text={__( 'Loading…', 'everyone-accessibility-suite' )} />
			) : (
				<div className="evas-overview-card__body">
					<div className="evas-overview-legend">
						<span className="evas-legend-item">
							<span className="evas-legend-dot evas-legend-dot--open"></span>
							{__( 'Panel opened', 'everyone-accessibility-suite' )}
						</span>
						<span className="evas-legend-item">
							<span className="evas-legend-dot evas-legend-dot--loaded"></span>
							{__( 'Panel loaded (hidden)', 'everyone-accessibility-suite' )}
						</span>
					</div>

					<canvas ref={canvasRef} className="evas-overview-canvas"></canvas>

					<div className="evas-overview-stats">
						<OverviewStat icon="hidden" label={__( 'Loaded (hidden)', 'everyone-accessibility-suite' )} value={summary?.loadedHidden ?? 0} />
						<OverviewStat icon="visibility" label={__( 'Panel opened', 'everyone-accessibility-suite' )} value={summary?.panelOpened ?? 0} highlight />
						<OverviewStat icon="admin-users" label={__( 'Unique users', 'everyone-accessibility-suite' )} value={summary?.uniqueUsers ?? 0} />
						<OverviewStat icon="update" label={__( 'Returning users', 'everyone-accessibility-suite' )} value={summary?.returningUsers ?? 0} />
					</div>
				</div>
			)}
		</section>
	);
};

/* -------------------------------------------------------------------------- */
/*  Top Used Modes card                                                        */
/* -------------------------------------------------------------------------- */

const TopModesCard = ({ enabled, loading, modesData }) => {
	const items = (modesData || []).filter((m) => (parseInt(m.value, 10) || 0) > 0).slice(0, 6);
	const total = items.reduce((sum, m) => sum + (parseInt(m.value, 10) || 0), 0);
	const max = Math.max(...items.map((m) => parseInt(m.value, 10) || 0), 1);

	return (
		<section className="evas-card evas-top-modes-card">
			<header className="evas-card__header">
				<h3>{__( 'Top Used Modes', 'everyone-accessibility-suite' )}</h3>
				<a href="#/usage-analytics" className="evas-card__header-link">{__( 'View All', 'everyone-accessibility-suite' )}</a>
			</header>

			<div className="evas-card__body">
				{!enabled ? (
					<p className="evas-card__empty">{__( 'Enable Usage Analytics to see feature usage.', 'everyone-accessibility-suite' )}</p>
				) : loading ? (
					<Loading text={__( 'Loading…', 'everyone-accessibility-suite' )} />
				) : items.length === 0 ? (
					<p className="evas-card__empty">{__( 'No data available', 'everyone-accessibility-suite' )}</p>
				) : (
					<ul className="evas-mode-list">
						{items.map((item) => {
							const value = parseInt(item.value, 10) || 0;
							const percent = total > 0 ? Math.round((value / total) * 100) : 0;
							return (
								<li key={item.slug || item.label} className="evas-mode-row">
									<ModeIcon slug={item.slug} />
									<span className="evas-mode-row__label">{item.label}</span>
									<span className="evas-mode-row__track">
										<span
											className="evas-mode-row__fill"
											style={{ width: `${(value / max) * 100}%` }}
										></span>
									</span>
									<span className="evas-mode-row__value">{value}</span>
									<span className="evas-mode-row__percent">({percent}%)</span>
								</li>
							);
						})}
					</ul>
				)}
			</div>
		</section>
	);
};

/* -------------------------------------------------------------------------- */
/*  Accessibility Analyzer card                                                */
/* -------------------------------------------------------------------------- */

/**
 * Ring colour follows the severity of what was found, not the ratio: a page
 * can pass most rules and still be broken for real users if any of the
 * failures are errors.
 */
const issueState = (report) => {
	if ((report.error_count || 0) > 0) return 'is-poor';
	if ((report.warning_count || 0) > 0) return 'is-warning';
	return 'is-good';
};

/**
 * What the ring counts. Live scans record how many axe-core rules passed out
 * of those that applied; reports saved before that (and server-side scans,
 * which have no rule concept) only carry a 0-100 score.
 */
const ringFigures = (report) => {
	const total = parseInt(report.total_checks, 10);
	const passed = parseInt(report.passed_checks, 10);

	if (Number.isInteger(total) && total > 0 && Number.isInteger(passed)) {
		return { value: passed, max: total, percent: (passed / total) * 100 };
	}

	const score = parseInt(report.score, 10) || 0;
	return { value: score, max: 100, percent: score };
};

const AnalyzerCard = ({ enabled }) => {
	const [loading, setLoading] = useState(true);
	const [rescanning, setRescanning] = useState(false);
	const [report, setReport] = useState(null);
	const [error, setError] = useState(null);

	const homeUrl = window.evasAdmin?.homeUrl || '';

	const loadLatest = useCallback(async () => {
		setLoading(true);
		setError(null);
		try {
			const response = await getAnalyzerFrontPage();
			setReport(response?.page || null);
		} catch (err) {
			setError(err.message || __( 'Failed to load analyzer data', 'everyone-accessibility-suite' ));
		} finally {
			setLoading(false);
		}
	}, []);

	useEffect(() => {
		if (enabled) {
			loadLatest();
		} else {
			setLoading(false);
		}
	}, [enabled, loadLatest]);

	// There is no server-side scan: axe-core needs computed styles and a
	// post-JS DOM, so a scan only happens in a real browser tab. Rescanning
	// therefore means re-opening the home page - clearing the scanner's
	// per-session "already scanned" list first, or it would skip the visit.
	const handleRescan = async () => {
		setRescanning(true);
		try {
			try {
				window.sessionStorage.removeItem(SCANNER_SESSION_KEY);
			} catch (e) {
				// sessionStorage unavailable - the scan still runs if this
				// browser session hasn't visited the home page yet.
			}

			window.open(homeUrl, '_blank', 'noopener');

			// The scan runs in that tab and POSTs its result; poll briefly for
			// a newer record rather than making the admin reload by hand.
			const before = report?.scanned_gmt || 0;
			for (let attempt = 0; attempt < 10; attempt++) {
				await new Promise((resolve) => setTimeout(resolve, 2000));
				const response = await getAnalyzerFrontPage();
				if ((response?.page?.scanned_gmt || 0) > before) {
					setReport(response.page);
					break;
				}
			}
		} catch (err) {
			setError(err.message || __( 'Scan failed', 'everyone-accessibility-suite' ));
		} finally {
			setRescanning(false);
		}
	};

	return (
		<section className="evas-card evas-analyzer-card">
			<header className="evas-card__header">
				<h3>{__( 'Accessibility Analyzer', 'everyone-accessibility-suite' )}</h3>
			</header>

			<div className="evas-card__body">
				{!enabled ? (
					<AnalyzerDisabledNotice />
				) : loading ? (
					<Loading text={__( 'Loading…', 'everyone-accessibility-suite' )} />
				) : error ? (
					<div className="evas-card__error">
						<p>{error}</p>
						<button type="button" className="button" onClick={loadLatest}>{__( 'Retry', 'everyone-accessibility-suite' )}</button>
					</div>
				) : report ? (
					<>
						<div className="evas-analyzer-card__score">
							{(() => {
								const ring = ringFigures(report);
								return (
									<div
										className={`evas-score-ring ${issueState(report)}`}
										style={{ '--score': ring.percent }}
									>
										<span className="evas-score-ring__value">{ring.value}</span>
										<span className="evas-score-ring__max">/ {ring.max}</span>
									</div>
								);
							})()}
							<div className="evas-analyzer-card__issues">
								<div className="evas-issue-row is-error">
									<span className="evas-issue-row__count">{report.error_count}</span>
									<span className="evas-issue-row__label">
										{__( 'Errors', 'everyone-accessibility-suite' )}
										<em>{__( 'Need immediate attention', 'everyone-accessibility-suite' )}</em>
									</span>
									<a href={`#/analyzer/${report.key}`} className="button button-small">{__( 'View', 'everyone-accessibility-suite' )}</a>
								</div>
								<div className="evas-issue-row is-warning">
									<span className="evas-issue-row__count">{report.warning_count}</span>
									<span className="evas-issue-row__label">
										{__( 'Warnings', 'everyone-accessibility-suite' )}
										{report.warning_count === 0
											? <em>{__( 'Good job! No warnings found', 'everyone-accessibility-suite' )}</em>
											: <em>{__( 'Review when possible', 'everyone-accessibility-suite' )}</em>}
									</span>
									<a href={`#/analyzer/${report.key}`} className="button button-small">{__( 'View', 'everyone-accessibility-suite' )}</a>
								</div>
							</div>
						</div>

						<div className="evas-analyzer-card__footer">
							<span className="evas-analyzer-card__meta">
								{__( 'Last scanned:', 'everyone-accessibility-suite' )} {report.scanned_at
									? new Date(report.scanned_at.replace(' ', 'T')).toLocaleString()
									: '—'}
							</span>
							<div className="evas-analyzer-card__actions">
								<button type="button" className="button" onClick={handleRescan} disabled={rescanning}>
									{rescanning ? __( 'Rescanning…', 'everyone-accessibility-suite' ) : __( 'Rescan', 'everyone-accessibility-suite' )}
								</button>
								<a href="#/analyzer" className="button button-primary">{__( 'Open Analyzer', 'everyone-accessibility-suite' )}</a>
							</div>
						</div>
					</>
				) : (
					<AnalyzerPendingNotice homeUrl={homeUrl} />
				)}
			</div>
		</section>
	);
};

/* -------------------------------------------------------------------------- */
/*  Quick Actions card                                                         */
/* -------------------------------------------------------------------------- */

const QuickActionsCard = ({ routes, customizerEnabled }) => {
	const hasRoute = (path) => routes.some((r) => r.path === path);

	// The card always shows six tiles: the preferred set first, then fillers
	// for whichever module pages aren't registered on this install.
	const candidates = [
		{ id: 'panel', title: __( 'Panel Settings', 'everyone-accessibility-suite' ), desc: __( 'Configure panel behavior', 'everyone-accessibility-suite' ), icon: 'admin-settings', href: '#/accessibility', show: true },
		{ id: 'customizer', title: __( 'Customize Appearance', 'everyone-accessibility-suite' ), desc: __( 'Design & layout options', 'everyone-accessibility-suite' ), icon: 'admin-customizer', href: '#/customizer', show: customizerEnabled || hasRoute('/customizer') },
		{ id: 'components', title: __( 'Manage Features', 'everyone-accessibility-suite' ), desc: __( 'Enable/disable features', 'everyone-accessibility-suite' ), icon: 'admin-plugins', href: '#/components', show: true },
		{ id: 'tts', title: __( 'Voice Settings', 'everyone-accessibility-suite' ), desc: __( 'TTS & voice configuration', 'everyone-accessibility-suite' ), icon: 'megaphone', href: '#/text-to-speech', show: hasRoute('/text-to-speech') },
		// Dashicons has no keyboard glyph; this is the icon the Virtual
		// Keyboard module registers for itself in the sidebar.
		{ id: 'keyboard', title: __( 'Keyboard Settings', 'everyone-accessibility-suite' ), desc: __( 'Virtual keyboard options', 'everyone-accessibility-suite' ), icon: 'editor-textcolor', href: '#/virtual-keyboard', show: hasRoute('/virtual-keyboard') },
		{ id: 'statement', title: __( 'Accessibility Statement', 'everyone-accessibility-suite' ), desc: __( 'Manage accessibility statement', 'everyone-accessibility-suite' ), icon: 'media-text', href: '#/statement', show: hasRoute('/statement') },
		// Fillers, only used when one of the above is unavailable.
		{ id: 'analyzer', title: __( 'Accessibility Analyzer', 'everyone-accessibility-suite' ), desc: __( 'WCAG compliance scanner', 'everyone-accessibility-suite' ), icon: 'search', href: '#/analyzer', show: hasRoute('/analyzer') },
		{ id: 'analytics', title: __( 'Usage Analytics', 'everyone-accessibility-suite' ), desc: __( 'Analytics & reports', 'everyone-accessibility-suite' ), icon: 'chart-area', href: '#/usage-analytics', show: hasRoute('/usage-analytics') },
		{ id: 'settings', title: __( 'Settings', 'everyone-accessibility-suite' ), desc: __( 'General plugin settings', 'everyone-accessibility-suite' ), icon: 'admin-generic', href: '#/settings', show: true },
	];

	const tiles = candidates.filter((c) => c.show).slice(0, 6);

	return (
		<section className="evas-card evas-quick-actions-card">
			<header className="evas-card__header">
				<h3>{__( 'Quick Actions', 'everyone-accessibility-suite' )}</h3>
			</header>
			<div className="evas-card__body">
				<div className="evas-action-grid">
					{tiles.map((tile) => (
						<a key={tile.id} href={tile.href} className="evas-action-tile">
							<span className="evas-action-tile__icon">
								<span className={`dashicons dashicons-${tile.icon}`}></span>
							</span>
							<span className="evas-action-tile__text">
								<span className="evas-action-tile__title">{tile.title}</span>
								<span className="evas-action-tile__desc">{tile.desc}</span>
							</span>
						</a>
					))}
				</div>
			</div>
		</section>
	);
};

/* -------------------------------------------------------------------------- */
/*  Page                                                                       */
/* -------------------------------------------------------------------------- */

function DashboardPage() {
	const { i18n, docsUrl, routes = [] } = window.evasAdmin || {};

	const [settings, setSettings] = useState({ enabled: false, panel_position: 'bottom-right', hide_on_mobile: false });
	const [modulesLoading, setModulesLoading] = useState(true);
	const [savingPanel, setSavingPanel] = useState(false);

	const [analyticsEnabled, setAnalyticsEnabled] = useState(false);
	const [analyzerEnabled, setAnalyzerEnabled] = useState(false);
	const [customizerEnabled, setCustomizerEnabled] = useState(false);

	const [range, setRange] = useState(defaultRange);
	const [granularity, setGranularity] = useState('hour');

	const [summary, setSummary] = useState(null);
	const [hourlyData, setHourlyData] = useState(null);
	const [modesData, setModesData] = useState(null);
	const [analyticsLoading, setAnalyticsLoading] = useState(true);

	// Modules + panel settings - load once.
	useEffect(() => {
		(async () => {
			try {
				const accessibilitySettings = await getAccessibilitySettings();
				setSettings({
					enabled: accessibilitySettings.enabled === true || accessibilitySettings.enabled === 'on',
					panel_position: accessibilitySettings.panel_position || 'bottom-right',
					hide_on_mobile: accessibilitySettings.hide_on_mobile === true || accessibilitySettings.hide_on_mobile === 'on',
				});

				const modulesData = await getModules();
				const list = modulesData?.modules || [];

				const analyticsModule = list.find((m) => m.id === 'usage_analytics');
				if (analyticsModule?.enabled) {
					try {
						const s = await getAnalyticsSettings();
						if (s?.settings?.enabled) setAnalyticsEnabled(true);
					} catch (e) {
						// module not fully available
					}
				}
				if (list.find((m) => m.id === 'customizer')?.enabled) setCustomizerEnabled(true);
				if (list.find((m) => m.id === 'accessibility_analyzer')?.enabled) setAnalyzerEnabled(true);
			} catch (error) {
				console.error('Error loading dashboard data:', error);
			} finally {
				setModulesLoading(false);
			}
		})();
	}, []);

	// Analytics data - reload on range / granularity / enabled changes.
	useEffect(() => {
		if (!analyticsEnabled) {
			setAnalyticsLoading(false);
			return;
		}

		let cancelled = false;
		(async () => {
			setAnalyticsLoading(true);
			try {
				const [summaryRes, hourlyRes, modesRes] = await Promise.all([
					getAnalyticsSummary(range),
					getAnalyticsStats('hourly-usage-chart', range, granularity),
					getAnalyticsStats('modes-chart', range),
				]);
				if (cancelled) return;
				if (summaryRes?.success) setSummary(summaryRes.data);
				setHourlyData(hourlyRes?.success && hourlyRes.data ? hourlyRes.data : null);
				setModesData(modesRes?.success && Array.isArray(modesRes.data) ? modesRes.data : []);
			} catch (error) {
				if (!cancelled) console.error('Error loading analytics:', error);
			} finally {
				if (!cancelled) setAnalyticsLoading(false);
			}
		})();

		return () => { cancelled = true; };
	}, [analyticsEnabled, range, granularity]);

	const handleSettingChange = useCallback(async (key, value) => {
		const previous = settings;
		const newSettings = { ...settings, [key]: value };

		// Applied straight away so the card flips without a round-trip, then
		// rolled back if the save doesn't land - otherwise the dashboard would
		// keep claiming the panel is live when it isn't.
		setSettings(newSettings);
		setSavingPanel(true);

		try {
			await updateAccessibilitySettings({
				enabled: newSettings.enabled,
				panel_position: newSettings.panel_position,
				hide_on_mobile: newSettings.hide_on_mobile ? 'on' : 'off',
			});
		} catch (error) {
			console.error('Error saving setting:', error);
			setSettings(previous);
		} finally {
			setSavingPanel(false);
		}
	}, [settings]);

	const deltas = summary?.delta || {};

	// The window the deltas are measured against: the same length, ending just
	// before the current one starts. The -1 keeps the label off the current
	// window's first day, so a Aug 21-27 range reads "vs. Aug 14 - Aug 20".
	const previousRangeLabel = formatRangeLabel(range.from - (range.to - range.from), range.from - 1);

	return (
		<div className="evas-dashboard-page">
			<PageHeader
				title={i18n?.dashboard || __( 'Dashboard', 'everyone-accessibility-suite' )}
				description={__( 'Welcome to Everyone Accessibility Suite', 'everyone-accessibility-suite' )}
				actions={<DateRangePicker value={range} onChange={setRange} />}
			/>

			{/* KPI row */}
			<div className="evas-kpi-grid">
				<KpiCard
					icon="universal-access"
					iconTone="success"
					label={__( 'Panel Status', 'everyone-accessibility-suite' )}
					value={settings.enabled ? __( 'Active', 'everyone-accessibility-suite' ) : __( 'Inactive', 'everyone-accessibility-suite' )}
					valueClass={settings.enabled ? 'is-active' : 'is-inactive'}
					hint={settings.enabled
						? __( 'The accessibility panel is live on your website.', 'everyone-accessibility-suite' )
						: __( 'The accessibility panel is hidden from visitors.', 'everyone-accessibility-suite' )}
				>
					<div className="evas-kpi-card__foot">
						<button
							type="button"
							className={`button ${settings.enabled ? '' : 'button-primary'}`}
							onClick={() => handleSettingChange('enabled', !settings.enabled)}
							disabled={modulesLoading || savingPanel}
						>
							{savingPanel
								? __( 'Saving…', 'everyone-accessibility-suite' )
								: settings.enabled
									? __( 'Disable Panel', 'everyone-accessibility-suite' )
									: __( 'Enable Panel', 'everyone-accessibility-suite' )}
						</button>
					</div>
				</KpiCard>

				<KpiCard
					icon="groups"
					label={__( 'Total Sessions', 'everyone-accessibility-suite' )}
					value={analyticsEnabled ? (summary?.sessions ?? '0') : '—'}
					badge={analyticsEnabled ? <DeltaBadge value={deltas.sessions} /> : null}
					hint={analyticsEnabled ? sprintf( /* translators: %s: label of the previous comparison period, e.g. "Aug 14 - Aug 20" */ __( 'vs. %s', 'everyone-accessibility-suite' ), previousRangeLabel ) : __( 'Usage Analytics is off', 'everyone-accessibility-suite' )}
				/>

				<KpiCard
					icon="chart-bar"
					label={__( 'Usage Rate', 'everyone-accessibility-suite' )}
					value={analyticsEnabled ? (summary?.usage ?? '0%') : '—'}
					badge={analyticsEnabled ? <DeltaBadge value={deltas.usage} /> : null}
					hint={analyticsEnabled ? __( 'of total visitors', 'everyone-accessibility-suite' ) : __( 'Usage Analytics is off', 'everyone-accessibility-suite' )}
				/>

				<KpiCard
					icon="clock"
					label={__( 'Avg. Time', 'everyone-accessibility-suite' )}
					value={analyticsEnabled ? (summary?.openTimer ?? '0 s.') : '—'}
					badge={analyticsEnabled ? <DeltaBadge value={deltas.avgTime} suffix=" s." /> : null}
					hint={analyticsEnabled ? __( 'average time spent', 'everyone-accessibility-suite' ) : __( 'Usage Analytics is off', 'everyone-accessibility-suite' )}
				/>
			</div>

			{/* Main grid */}
			<div className="evas-dashboard-main">
				<UsageOverviewCard
					enabled={analyticsEnabled}
					loading={analyticsLoading}
					summary={summary}
					hourlyData={hourlyData}
					granularity={granularity}
					onGranularityChange={setGranularity}
				/>
				<TopModesCard
					enabled={analyticsEnabled}
					loading={analyticsLoading}
					modesData={modesData}
				/>
			</div>

			{/* Secondary grid */}
			<div className="evas-dashboard-secondary">
				<AnalyzerCard enabled={analyzerEnabled} />
				<QuickActionsCard routes={routes} customizerEnabled={customizerEnabled} />
			</div>

			{/* Banner */}
			<div className="evas-dashboard-banner">
				<span className="dashicons dashicons-shield"></span>
				<div className="evas-dashboard-banner__text">
					<strong>{__( 'Build a more inclusive web for everyone', 'everyone-accessibility-suite' )}</strong>
					<span>
						{__( 'Everyone Accessibility Suite helps you meet accessibility standards and create better experiences for all users.', 'everyone-accessibility-suite' )}
					</span>
				</div>
				{docsUrl && (
					<a href={docsUrl} target="_blank" rel="noopener noreferrer" className="button button-primary">
						{__( 'View Documentation', 'everyone-accessibility-suite' )}
					</a>
				)}
			</div>
		</div>
	);
}

export default DashboardPage;
