/**
 * Accessibility Analyzer Page
 *
 * Lists the frontend pages an admin has visited (each scanned automatically
 * by axe-core in that browser, see assets/accessibility/js/live-scanner.js)
 * and drills into one page's issues.
 *
 * There is no "scan a URL" form here on purpose: accessibility rules need
 * computed styles, ARIA state and a post-JS DOM, so a scan can only happen
 * in a real browser tab showing the page. Rescanning therefore means
 * opening the page, exactly as Elementor's Ally does.
 *
 * @package Everyone_Accessibility_Suite
 */

import { useState, useEffect, useCallback } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { useParams, useNavigate } from 'react-router-dom';
import {
	getAnalyzerPages,
	getAnalyzerPage,
	deleteAnalyzerPage,
	clearAnalyzerPages,
	getAnalyzerSettings,
	updateAnalyzerSettings,
} from '../services/settings-api';
import { PageHeader, Loading, Notice, Toggle } from '../components/ui';

// Most severe first - drives both the issue-group ordering and the chips.
const IMPACTS = ['critical', 'serious', 'moderate', 'minor'];

const IMPACT_LABELS = {
	critical: __( 'Critical', 'everyone-accessibility-suite' ),
	serious: __( 'Serious', 'everyone-accessibility-suite' ),
	moderate: __( 'Moderate', 'everyone-accessibility-suite' ),
	minor: __( 'Minor', 'everyone-accessibility-suite' ),
};

/**
 * Score colour band, matching the dashboard card's ring states.
 *
 * @param {Object} page Page row.
 * @return {string} Modifier class.
 */
const scoreState = (page) => {
	if ((page.error_count || 0) > 0) return 'is-poor';
	if ((page.warning_count || 0) > 0) return 'is-warning';
	return 'is-good';
};

/**
 * Prefer the real rule tally (axe rules passed / rules run) over the
 * synthetic score, same rule the dashboard card uses.
 *
 * @param {Object} page Page row.
 * @return {Object} { value, max, percent }
 */
const ringFigures = (page) => {
	const passed = parseInt(page.passed_checks, 10);
	const total = parseInt(page.total_checks, 10);

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

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

/**
 * "3 minutes ago" style label. Falls back to the raw timestamp so a value
 * this can't parse is still shown rather than swallowed.
 *
 * @param {number} gmt  Unix timestamp.
 * @param {string} text Formatted fallback.
 * @return {string}
 */
const relativeTime = (gmt, text) => {
	const seconds = Math.floor(Date.now() / 1000) - (parseInt(gmt, 10) || 0);

	if (!gmt || seconds < 0) return text || '—';
	if (seconds < 60) return __( 'Just now', 'everyone-accessibility-suite' );

	const minutes = Math.floor(seconds / 60);
	if (minutes < 60) {
		/* translators: %d: number of minutes. */
		return sprintf( __( '%d min ago', 'everyone-accessibility-suite' ), minutes );
	}

	const hours = Math.floor(minutes / 60);
	if (hours < 24) {
		/* translators: %d: number of hours. */
		return sprintf( __( '%dh ago', 'everyone-accessibility-suite' ), hours );
	}

	const days = Math.floor(hours / 24);
	/* translators: %d: number of days. */
	return sprintf( __( '%dd ago', 'everyone-accessibility-suite' ), days );
};

/**
 * Score ring, reused from the dashboard card. --size lets the same
 * component render small in a table cell.
 */
const ScoreRing = ({ page, size }) => {
	const ring = ringFigures(page);

	return (
		<div
			className={`evas-score-ring ${scoreState(page)}`}
			style={{ '--score': ring.percent, '--size': size ? `${size}px` : undefined }}
		>
			<span className="evas-score-ring__value">{ring.value}</span>
			<span className="evas-score-ring__max">/ {ring.max}</span>
		</div>
	);
};

const EmptyState = ({ autoScan, homeUrl }) => (
	<div className="evas-analyzer-empty">
		<span className="dashicons dashicons-search"></span>
		<h3>{__( 'No pages scanned yet', 'everyone-accessibility-suite' )}</h3>
		{autoScan ? (
			<p>
				{__( 'Open your site while logged in as an administrator - every frontend page you visit is checked automatically and shows up here.', 'everyone-accessibility-suite' )}
			</p>
		) : (
			<p>
				{__( 'Automatic scanning is off, so pages are only added when you use the "WCAG Scan" button in the admin bar. Turn it back on above to collect pages as you browse.', 'everyone-accessibility-suite' )}
			</p>
		)}
		<a href={homeUrl} target="_blank" rel="noopener noreferrer" className="button button-primary">
			{__( 'Open site', 'everyone-accessibility-suite' )}
		</a>
	</div>
);

/**
 * One axe rule and every element on the page that failed it. Grouping by
 * rule (rather than listing each element separately) matches how the work
 * is actually done: one fix usually clears all of a rule's occurrences.
 */
const IssueGroup = ({ group }) => {
	const [open, setOpen] = useState(false);
	const first = group.issues[0];

	return (
		<div className={`evas-issue-group is-${group.impact}`}>
			<button
				type="button"
				className="evas-issue-group__header"
				onClick={() => setOpen(!open)}
				aria-expanded={open}
			>
				<span className={`evas-impact-chip is-${group.impact}`}>
					{IMPACT_LABELS[group.impact] || group.impact}
				</span>
				<span className="evas-issue-group__title">{group.criterion}</span>
				{first.wcag && <span className="evas-wcag-chip">{`WCAG ${first.wcag}`}</span>}
				<span className="evas-issue-group__count">
					{sprintf(
						/* translators: %d: number of affected elements. */
						__( '%d occurrences', 'everyone-accessibility-suite' ),
						group.issues.length
					)}
				</span>
				<span className={`dashicons dashicons-arrow-${open ? 'up' : 'down'}-alt2`}></span>
			</button>

			{open && (
				<div className="evas-issue-group__body">
					{first.description && <p className="evas-issue-group__desc">{first.description}</p>}
					{first.helpUrl && (
						<p>
							<a href={first.helpUrl} target="_blank" rel="noopener noreferrer">
								{__( 'Learn more', 'everyone-accessibility-suite' )}
							</a>
						</p>
					)}

					<ul className="evas-issue-nodes">
						{group.issues.map((issue, index) => (
							<li key={index}>
								{issue.selector && <code>{issue.selector}</code>}
								{/* Rendered as text, never as markup - this is
								    page HTML captured from the frontend. */}
								{issue.context && <pre>{issue.context}</pre>}
								{issue.suggestion && (
									<p className="evas-issue-nodes__fix">{issue.suggestion}</p>
								)}
							</li>
						))}
					</ul>
				</div>
			)}
		</div>
	);
};

/**
 * Detail view for one page.
 */
const PageDetail = ({ pageKey, onBack, onDeleted }) => {
	const [loading, setLoading] = useState(true);
	const [page, setPage] = useState(null);
	const [error, setError] = useState(null);
	const [impactFilter, setImpactFilter] = useState('all');

	useEffect(() => {
		let cancelled = false;

		(async () => {
			setLoading(true);
			setError(null);
			try {
				const response = await getAnalyzerPage(pageKey);
				if (!cancelled) setPage(response?.page || null);
			} catch (err) {
				if (!cancelled) setError(err.message || __( 'Failed to load the report', 'everyone-accessibility-suite' ));
			} finally {
				if (!cancelled) setLoading(false);
			}
		})();

		return () => { cancelled = true; };
	}, [pageKey]);

	const handleDelete = async () => {
		// eslint-disable-next-line no-alert
		if (!window.confirm( __( 'Delete this page report?', 'everyone-accessibility-suite' ) )) return;
		await deleteAnalyzerPage(pageKey);
		onDeleted();
	};

	if (loading) return <Loading text={__( 'Loading…', 'everyone-accessibility-suite' )} />;
	if (error) return <Notice type="error">{error}</Notice>;
	if (!page) return <Notice type="warning">{__( 'This report no longer exists.', 'everyone-accessibility-suite' )}</Notice>;

	const issues = page.issues || [];
	const counts = IMPACTS.reduce((acc, impact) => {
		acc[impact] = issues.filter((i) => i.impact === impact).length;
		return acc;
	}, {});

	const visible = impactFilter === 'all'
		? issues
		: issues.filter((i) => i.impact === impactFilter);

	// Group by rule, then order groups by severity and by how many
	// elements each one affects.
	const groups = Object.values(
		visible.reduce((acc, issue) => {
			const key = issue.criterion || issue.description || 'other';
			if (!acc[key]) {
				acc[key] = { criterion: key, impact: issue.impact, issues: [] };
			}
			if (IMPACTS.indexOf(issue.impact) < IMPACTS.indexOf(acc[key].impact)) {
				acc[key].impact = issue.impact;
			}
			acc[key].issues.push(issue);
			return acc;
		}, {})
	).sort((a, b) => {
		const bySeverity = IMPACTS.indexOf(a.impact) - IMPACTS.indexOf(b.impact);
		return bySeverity !== 0 ? bySeverity : b.issues.length - a.issues.length;
	});

	return (
		<div className="evas-analyzer-detail">
			<button type="button" className="evas-analyzer-back" onClick={onBack}>
				<span className="dashicons dashicons-arrow-left-alt2"></span>
				{__( 'All pages', 'everyone-accessibility-suite' )}
			</button>

			<div className="evas-analyzer-detail__head">
				<ScoreRing page={page} />
				<div className="evas-analyzer-detail__meta">
					<h3>{page.title}</h3>
					<a href={page.url} target="_blank" rel="noopener noreferrer">{page.url}</a>
					<p className="evas-analyzer-detail__stats">
						<strong>{page.error_count}</strong> {__( 'errors', 'everyone-accessibility-suite' )}
						{' · '}
						<strong>{page.warning_count}</strong> {__( 'warnings', 'everyone-accessibility-suite' )}
						{' · '}
						{sprintf(
							/* translators: %s: date and time. */
							__( 'scanned %s', 'everyone-accessibility-suite' ),
							page.scanned_at || '—'
						)}
					</p>
					<div className="evas-analyzer-detail__actions">
						<a href={page.url} target="_blank" rel="noopener noreferrer" className="button button-primary">
							{__( 'Open & rescan', 'everyone-accessibility-suite' )}
						</a>
						<button type="button" className="button" onClick={handleDelete}>
							{__( 'Delete report', 'everyone-accessibility-suite' )}
						</button>
					</div>
				</div>
			</div>

			{page.truncated && (
				<Notice type="warning">
					{sprintf(
						/* translators: %d: number of stored issues. */
						__( 'This page had more issues than are kept per page; only the %d most severe are stored.', 'everyone-accessibility-suite' ),
						issues.length
					)}
				</Notice>
			)}

			{issues.length === 0 ? (
				<Notice type="success">
					{__( 'No issues found for the checked WCAG rules.', 'everyone-accessibility-suite' )}
				</Notice>
			) : (
				<>
					<div className="evas-analyzer-filters">
						<button
							type="button"
							className={`evas-filter-chip ${impactFilter === 'all' ? 'is-active' : ''}`}
							onClick={() => setImpactFilter('all')}
						>
							{__( 'All', 'everyone-accessibility-suite' )} ({issues.length})
						</button>
						{IMPACTS.filter((impact) => counts[impact] > 0).map((impact) => (
							<button
								key={impact}
								type="button"
								className={`evas-filter-chip is-${impact} ${impactFilter === impact ? 'is-active' : ''}`}
								onClick={() => setImpactFilter(impact)}
							>
								{IMPACT_LABELS[impact]} ({counts[impact]})
							</button>
						))}
					</div>

					<div className="evas-issue-groups">
						{groups.map((group) => (
							<IssueGroup key={group.criterion} group={group} />
						))}
					</div>
				</>
			)}
		</div>
	);
};

/**
 * List of every scanned page.
 */
const PageList = ({ pages, summary, autoScan, homeUrl, onOpen, onDelete, onClear }) => {
	const [search, setSearch] = useState('');
	const [filter, setFilter] = useState('all');
	const [sort, setSort] = useState('recent');

	if (pages.length === 0) {
		return <EmptyState autoScan={autoScan} homeUrl={homeUrl} />;
	}

	const term = search.trim().toLowerCase();

	const visible = pages
		.filter((page) => {
			if (filter === 'errors' && (page.error_count || 0) === 0) return false;
			if (filter === 'warnings' && ((page.warning_count || 0) === 0 || (page.error_count || 0) > 0)) return false;
			if (filter === 'clean' && ((page.error_count || 0) > 0 || (page.warning_count || 0) > 0)) return false;
			if (!term) return true;
			return `${page.title} ${page.url}`.toLowerCase().includes(term);
		})
		.sort((a, b) => {
			switch (sort) {
				case 'score':
					return (a.score || 0) - (b.score || 0);
				case 'errors':
					return (b.error_count || 0) - (a.error_count || 0);
				case 'title':
					return String(a.title).localeCompare(String(b.title));
				default:
					return (b.scanned_gmt || 0) - (a.scanned_gmt || 0);
			}
		});

	return (
		<>
			<div className="evas-analyzer-summary">
				<ScoreRing page={{ score: summary.avg_score, error_count: summary.total_errors, warning_count: summary.total_warnings }} />
				<div className="evas-analyzer-summary__tiles">
					<div className="evas-summary-tile">
						<span className="evas-summary-tile__value">{summary.pages}</span>
						<span className="evas-summary-tile__label">{__( 'Pages scanned', 'everyone-accessibility-suite' )}</span>
					</div>
					<div className="evas-summary-tile">
						<span className="evas-summary-tile__value">{summary.pages_with_errors}</span>
						<span className="evas-summary-tile__label">{__( 'Pages with errors', 'everyone-accessibility-suite' )}</span>
					</div>
					<div className="evas-summary-tile is-error">
						<span className="evas-summary-tile__value">{summary.total_errors}</span>
						<span className="evas-summary-tile__label">{__( 'Total errors', 'everyone-accessibility-suite' )}</span>
					</div>
					<div className="evas-summary-tile is-warning">
						<span className="evas-summary-tile__value">{summary.total_warnings}</span>
						<span className="evas-summary-tile__label">{__( 'Total warnings', 'everyone-accessibility-suite' )}</span>
					</div>
				</div>
			</div>

			<div className="evas-analyzer-toolbar">
				<input
					type="search"
					className="evas-analyzer-search"
					placeholder={__( 'Search pages…', 'everyone-accessibility-suite' )}
					value={search}
					onChange={(e) => setSearch(e.target.value)}
					aria-label={__( 'Search pages', 'everyone-accessibility-suite' )}
				/>

				<div className="evas-analyzer-filters">
					{[
						['all', __( 'All', 'everyone-accessibility-suite' )],
						['errors', __( 'With errors', 'everyone-accessibility-suite' )],
						['warnings', __( 'Warnings only', 'everyone-accessibility-suite' )],
						['clean', __( 'Clean', 'everyone-accessibility-suite' )],
					].map(([value, label]) => (
						<button
							key={value}
							type="button"
							className={`evas-filter-chip ${filter === value ? 'is-active' : ''}`}
							onClick={() => setFilter(value)}
						>
							{label}
						</button>
					))}
				</div>

				<select
					className="evas-analyzer-sort"
					value={sort}
					onChange={(e) => setSort(e.target.value)}
					aria-label={__( 'Sort pages', 'everyone-accessibility-suite' )}
				>
					<option value="recent">{__( 'Last scanned', 'everyone-accessibility-suite' )}</option>
					<option value="score">{__( 'Lowest score', 'everyone-accessibility-suite' )}</option>
					<option value="errors">{__( 'Most errors', 'everyone-accessibility-suite' )}</option>
					<option value="title">{__( 'Title A-Z', 'everyone-accessibility-suite' )}</option>
				</select>

				<button type="button" className="button evas-analyzer-clear" onClick={onClear}>
					{__( 'Clear all', 'everyone-accessibility-suite' )}
				</button>
			</div>

			{visible.length === 0 ? (
				<Notice type="info">{__( 'No pages match the current filters.', 'everyone-accessibility-suite' )}</Notice>
			) : (
				<table className="evas-analyzer-table">
					<thead>
						<tr>
							<th>{__( 'Page', 'everyone-accessibility-suite' )}</th>
							<th>{__( 'Score', 'everyone-accessibility-suite' )}</th>
							<th>{__( 'Errors', 'everyone-accessibility-suite' )}</th>
							<th>{__( 'Warnings', 'everyone-accessibility-suite' )}</th>
							<th>{__( 'Last scan', 'everyone-accessibility-suite' )}</th>
							<th className="evas-analyzer-table__actions">{__( 'Actions', 'everyone-accessibility-suite' )}</th>
						</tr>
					</thead>
					<tbody>
						{visible.map((page) => (
							<tr key={page.key}>
								<td className="evas-analyzer-table__page-cell">
									<button
										type="button"
										className="evas-analyzer-table__page"
										onClick={() => onOpen(page.key)}
										title={`${page.title}\n${page.url}`}
									>
										<strong>{page.title}</strong>
										<span>{page.url}</span>
									</button>
								</td>
								<td><ScoreRing page={page} size={44} /></td>
								<td className={(page.error_count || 0) > 0 ? 'is-error' : ''}>{page.error_count}</td>
								<td className={(page.warning_count || 0) > 0 ? 'is-warning' : ''}>{page.warning_count}</td>
								<td title={page.scanned_at}>{relativeTime(page.scanned_gmt, page.scanned_at)}</td>
								<td className="evas-analyzer-table__actions">
									{/* The flex row lives in a wrapper, not on the
									    <td> itself: display:flex on a table cell
									    drops it out of the table layout, which
									    broke the column widths and row borders. */}
									{/* Only the primary action keeps a label - three
									    text buttons took ~300px of a ~900px table
									    and left the page column with nothing. */}
									<div className="evas-analyzer-table__actions-row">
										<button type="button" className="button button-small" onClick={() => onOpen(page.key)}>
											{__( 'View issues', 'everyone-accessibility-suite' )}
										</button>
										<a
											href={page.url}
											target="_blank"
											rel="noopener noreferrer"
											className="button button-small evas-icon-button"
											title={__( 'Open page', 'everyone-accessibility-suite' )}
											aria-label={__( 'Open page', 'everyone-accessibility-suite' )}
										>
											<span className="dashicons dashicons-external"></span>
										</a>
										<button
											type="button"
											className="button button-small evas-icon-button"
											onClick={() => onDelete(page.key)}
											title={__( 'Delete report', 'everyone-accessibility-suite' )}
											aria-label={__( 'Delete report', 'everyone-accessibility-suite' )}
										>
											<span className="dashicons dashicons-trash"></span>
										</button>
									</div>
								</td>
							</tr>
						))}
					</tbody>
				</table>
			)}
		</>
	);
};

function AnalyzerPage() {
	const { pageKey } = useParams();
	const navigate = useNavigate();

	const [loading, setLoading] = useState(true);
	const [error, setError] = useState(null);
	const [pages, setPages] = useState([]);
	const [summary, setSummary] = useState({ pages: 0, pages_with_errors: 0, total_errors: 0, total_warnings: 0, avg_score: 100, last_scan: null });
	const [autoScan, setAutoScan] = useState(true);
	const [savingSetting, setSavingSetting] = useState(false);

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

	const load = useCallback(async () => {
		setLoading(true);
		setError(null);
		try {
			const [pagesResponse, settingsResponse] = await Promise.all([
				getAnalyzerPages(),
				getAnalyzerSettings(),
			]);
			setPages(pagesResponse?.pages || []);
			if (pagesResponse?.summary) setSummary(pagesResponse.summary);
			setAutoScan(settingsResponse?.settings?.auto_scan !== false);
		} catch (err) {
			setError(err.message || __( 'Failed to load analyzer data', 'everyone-accessibility-suite' ));
		} finally {
			setLoading(false);
		}
	}, []);

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

	const handleAutoScan = async (value) => {
		setSavingSetting(true);
		setAutoScan(value);
		try {
			await updateAnalyzerSettings(value);
		} catch (err) {
			// Put the switch back if the server refused it.
			setAutoScan(!value);
			setError(err.message || __( 'Failed to save the setting', 'everyone-accessibility-suite' ));
		} finally {
			setSavingSetting(false);
		}
	};

	const handleDelete = async (key) => {
		// eslint-disable-next-line no-alert
		if (!window.confirm( __( 'Delete this page report?', 'everyone-accessibility-suite' ) )) return;
		await deleteAnalyzerPage(key);
		load();
	};

	const handleClear = async () => {
		// eslint-disable-next-line no-alert
		if (!window.confirm( __( 'Delete every stored page report? Pages are re-added as you browse the site.', 'everyone-accessibility-suite' ) )) return;
		await clearAnalyzerPages();
		load();
	};

	return (
		<div className="evas-analyzer-page">
			<PageHeader
				title={__( 'Accessibility Analyzer', 'everyone-accessibility-suite' )}
				description={__( 'Frontend pages are checked with axe-core as you open them while logged in as an administrator.', 'everyone-accessibility-suite' )}
				actions={
					<Toggle
						id="evas-analyzer-auto-scan"
						label={__( 'Auto-scan visited pages', 'everyone-accessibility-suite' )}
						checked={autoScan}
						disabled={savingSetting}
						onChange={handleAutoScan}
					/>
				}
			/>

			{error && <Notice type="error">{error}</Notice>}

			{loading ? (
				<Loading text={__( 'Loading…', 'everyone-accessibility-suite' )} />
			) : pageKey ? (
				<PageDetail
					pageKey={pageKey}
					onBack={() => navigate('/analyzer')}
					onDeleted={() => { navigate('/analyzer'); load(); }}
				/>
			) : (
				<PageList
					pages={pages}
					summary={summary}
					autoScan={autoScan}
					homeUrl={homeUrl}
					onOpen={(key) => navigate(`/analyzer/${key}`)}
					onDelete={handleDelete}
					onClear={handleClear}
				/>
			)}
		</div>
	);
}

export default AnalyzerPage;
