import { __, _n, sprintf } from '@wordpress/i18n';
import React, { useEffect, useRef, useState } from 'react';
import { api } from '../api';
import { Card, Button, Badge, Notice, MetricGrid, ChipBar, PageState, SegmentBar, Progress } from '../components/ui';
import ScreenVerdict from '../components/ScreenVerdict';
import FirstRun from '../components/FirstRun';
import DataGrid, { useServerQuery } from '../components/DataGrid';

function StatusBadge({ status }) {
	if (status === 0) return <Badge tone="serious">unreachable</Badge>;
	if (status >= 500) return <Badge tone="serious">{status}</Badge>;
	if (status >= 400) return <Badge tone="serious">{status}</Badge>;
	if (status >= 300) return <Badge tone="warning">{status} redirect</Badge>;
	return <Badge tone="good">{status}</Badge>;
}

export default function LinkChecker() {
	const [report, setReport] = useState(null);
	const [scanning, setScanning] = useState(false);

	/*
	 * Where the results actually are.
	 *
	 * The verdict at the top offers "Show broken links", and its only effect
	 * was to set a filter on a table far below the fold. Nothing moved on the
	 * screen the reader was looking at, which is indistinguishable from a
	 * button that does nothing — and that is how it was reported. Filtering
	 * without going there is only half the action.
	 */
	const resultsRef = useRef(null);
	const showOnly = (value) => {
		grid.setFilter('status', value);
		// After the filter, so the grid has re-rendered by the time we move.
		window.requestAnimationFrame(() => {
			resultsRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
		});
	};
	const [error, setError] = useState(null);
	const [rechecking, setRechecking] = useState(null);
	const stop = useRef(false);

	// The full found-links table, server-paged: at fifty thousand links the
	// browser holds one page, and the old 500-row report cap is gone.
	const grid = useServerQuery('/linkcheck/links', {
		orderby: 'status',
		order: 'desc',
		filters: { status: 'issues' },
	});

	const load = () => api.get('/linkcheck/report').then(setReport).catch((e) => setError(e.message));
	useEffect(() => {
		load();
		return () => {
			stop.current = true;
		};
	}, []);

	const scan = async () => {
		setScanning(true);
		setError(null);
		stop.current = false;
		try {
			await api.post('/linkcheck/start');
			let job = await api.post('/linkcheck/tick');
			while (job.state === 'running' && !stop.current) {
				if (job.busy || job.error) await new Promise((r) => setTimeout(r, 2000));
				job = await api.post('/linkcheck/tick');
				// Refresh the report every few ticks so numbers move live.
				if ((job.checked || 0) % 24 < 8) await load();
				setReport((r) => (r ? { ...r, job } : r));
			}
			await load();
			grid.refresh();
		} catch (e) {
			setError(e.message);
		} finally {
			setScanning(false);
		}
	};

	const recheck = async (url) => {
		setRechecking(url);
		try {
			setReport(await api.post('/linkcheck/recheck', { url }));
			grid.refresh();
		} catch (e) {
			setError(e.message);
		} finally {
			setRechecking(null);
		}
	};

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

	const { totals, job } = report;
	const bad = totals.broken + totals.unreachable;
	const destinations = report.by_destination || [];
	const hosts = report.hosts || [];

	// The path of an internal URL, for the redirect form.
	const pathOf = (url) => {
		try {
			const u = new URL(url, window.location.origin);
			return u.pathname + u.search;
		} catch (e) {
			return url;
		}
	};
	const toRedirect = (url) => {
		window.location.hash = `/links/redirects?from=${encodeURIComponent(pathOf(url))}`;
	};

	// No completed scan → no verdict, whatever partial rows exist.
	const verdict =
		!report?.snapshot || totals.total === 0
			? null
			: bad > 0
				? {
						tone: 'bad',
						text:
							sprintf(
								/* translators: 1: broken links, 2: links checked. */
								_n('%1$s of %2$s links is broken.', '%1$s of %2$s links are broken.', bad, 'ai-rank-suite'),
								bad.toLocaleString(),
								totals.total.toLocaleString()
							) +
							(destinations.length
								? ' ' +
									sprintf(
										_n('%s distinct destination.', '%s distinct destinations.', destinations.length, 'ai-rank-suite'),
										destinations.length === 20 ? '20+' : destinations.length
									)
								: ''),
						why: __('A dead link is a trust signal pointing the wrong way, for readers and for crawlers. Fix by destination: one redirect or one edited URL clears every page that points there.', 'ai-rank-suite'),
						action: __('Show broken links', 'ai-rank-suite'),
						onAction: () => showOnly('broken'),
					}
				: totals.redirected > 0
					? {
							tone: 'warning',
							text: sprintf(__('No broken links, but %s still go through a redirect hop.', 'ai-rank-suite'), totals.redirected.toLocaleString()),
							why: __('Each hop costs crawl budget and a few hundred milliseconds. Update them to the final URL when you touch the page.', 'ai-rank-suite'),
							action: __('Show redirected', 'ai-rank-suite'),
							onAction: () => showOnly('redirected'),
						}
					: {
							tone: 'good',
							text: sprintf(__('All %s links are healthy.', 'ai-rank-suite'), totals.total.toLocaleString()),
							why: __('Scan again after a publishing sprint or a domain change.', 'ai-rank-suite'),
						};
	// Progress is the current phase's own ratio — never a blend of two
	// phases, which once showed "176/255 · 85%". Phase 1 reads pages, phase 2
	// checks the unique destinations; each is reported on its own scale.
	const snapshot = report?.snapshot || null;
	const live = report?.running || null;
	const phaseDone = job.phase === 'extract' ? job.done_posts || 0 : job.checked || 0;
	const phaseTotal = job.phase === 'extract' ? job.total_posts || 0 : job.total_urls || 0;
	const progress = job.state === 'running' ? Math.min(100, Math.round((100 * phaseDone) / Math.max(1, phaseTotal))) : null;

	/*
	 * Every destination checked, and the run not closed yet.
	 *
	 * The scan needs one more tick after the last destination to find nothing
	 * left and write its results, and that tick can be slow — it follows a
	 * batch of HTTP requests, or waits on cron if the tab was closed. What the
	 * reader saw meanwhile was a full bar over the words "checking
	 * destinations 215 / 215", which is indistinguishable from a scan that has
	 * hung. It says what it is doing instead.
	 */
	const finishing = job.state === 'running' && phaseTotal > 0 && phaseDone >= phaseTotal;

	return (
		<div className="ars-page">
			{error && <Notice tone="serious">{error}</Notice>}

			{/* A running job contains partial extraction/check data. Never turn
			    that partial table into a final health verdict. */}
			{verdict && !scanning && job.state !== 'running' && <ScreenVerdict {...verdict} />}

			{!report?.snapshot && !scanning && job.state !== 'running' && (
				<FirstRun
					badge={__('Not scanned yet', 'ai-rank-suite')}
					ringLabel={__('Link health', 'ai-rank-suite')}
					title={__('No link on this site has been checked yet.', 'ai-rank-suite')}
					why={__('The first scan reads every link in your published content — internal ones resolve instantly, external ones are verified over HTTP — and turns them into a health score, a broken-destinations list and a per-host view. On a large site it runs in slices; leave the tab open.', 'ai-rank-suite')}
					action={__('Start the first scan', 'ai-rank-suite')}
					onAction={scan}
					busy={scanning}
					stats={[
						{ label: __('Links found', 'ai-rank-suite'), hint: __('Every link extracted from published content.', 'ai-rank-suite') },
						{ label: __('Healthy', 'ai-rank-suite'), hint: __('Answering 2xx.', 'ai-rank-suite') },
						{ label: __('Redirected', 'ai-rank-suite'), hint: __('Resolving only after a hop.', 'ai-rank-suite') },
						{ label: __('Broken', 'ai-rank-suite'), hint: __('4xx, 5xx or unreachable.', 'ai-rank-suite') },
					]}
					previews={[
						{ title: __('Broken destinations', 'ai-rank-suite'), text: __('One row per dead target however many pages point at it — with a Redirect button that pre-fills the redirect form, and Recheck.', 'ai-rank-suite') },
						{ title: __('External hosts', 'ai-rank-suite'), text: __('Where your outbound links go and which of those hosts have gone dark, worst first.', 'ai-rank-suite') },
						{ title: __('Every link, filtered', 'ai-rank-suite'), text: __('Broken, redirected, healthy; internal or external; searchable by URL or anchor; server-paged for fifty thousand links.', 'ai-rank-suite') },
					]}
				/>
			)}

			{(report?.snapshot || scanning || job.state === 'running') && (
			<Card
				title={__('Site-wide scan', 'ai-rank-suite')}
				subtitle={__('Extracts every link from your published content, then checks each one — internal links resolve instantly, external ones are verified over HTTP', 'ai-rank-suite')}
				action={
					<Button onClick={scan} busy={scanning}>
						{scanning ? __('Scanning…', 'ai-rank-suite') : totals.total > 0 ? __('Scan again', 'ai-rank-suite') : __('Start scan', 'ai-rank-suite')}
					</Button>
				}
			>
				{progress !== null && (
					<div className="grid gap-1.5">
						<Progress
							label={
								job.phase === 'extract'
									? sprintf(__('Phase 1 of 2 — reading pages %1$s / %2$s', 'ai-rank-suite'), (job.done_posts || 0).toLocaleString(), (job.total_posts || 0).toLocaleString())
									: finishing
										? __('Every destination checked — writing the results', 'ai-rank-suite')
										: sprintf(__('Phase 2 of 2 — checking unique destinations %1$s / %2$s', 'ai-rank-suite'), (job.checked || 0).toLocaleString(), (job.total_urls || 0).toLocaleString())
							}
							total={100}
							percent={progress}
						/>
						{/* The numbers below belong to the run in progress and are
						    kept apart from the published snapshot on purpose —
						    a half-checked table is not a health verdict. */}
						{live && (
							<p className="m-0 t-caption">
								{/*
								  * Say which population each number counts.
								  * The bar above counts unique destinations and
								  * this line counts link occurrences, so a page
								  * could read "215 / 215" beside "403 checked"
								  * and look like two answers to one question.
								  * A site links to the same page many times;
								  * that is the whole reason the scan de-duplicates.
								  */}
								{sprintf(__('This run so far: %1$s links found across your pages, %2$s of them checked, %3$s broken. The bar above counts unique destinations, which is fewer — the same page is often linked many times.', 'ai-rank-suite'), live.found.toLocaleString(), live.checked.toLocaleString(), live.broken.toLocaleString())}{' '}
								{snapshot ? __('The figures below are still the last completed scan.', 'ai-rank-suite') : __('Nothing is published yet — results appear when this scan completes.', 'ai-rank-suite')}
								{' '}
								{/*
								  * This used to promise that closing the tab
								  * pauses the scan, because it did: the browser
								  * was the only thing ticking it, and a run
								  * abandoned at 84 of 215 destinations stayed
								  * there. Cron carries it now, so the tab is
								  * the fast way rather than the only way.
								  */}
								{__('Leaving this tab open is the quickest way to finish it. Close it and the scan carries on in the background, more slowly.', 'ai-rank-suite')}
							</p>
						)}
					</div>
				)}
				{snapshot && !scanning && job.state !== 'running' && (
					<p className="m-0 text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
						{snapshot.pages
							? sprintf(__('Showing the scan completed %1$s over %2$s pages.', 'ai-rank-suite'), new Date((snapshot.finished_at || 0) * 1000).toLocaleString(), snapshot.pages.toLocaleString())
							: sprintf(__('Showing the scan completed %s.', 'ai-rank-suite'), new Date((snapshot.finished_at || 0) * 1000).toLocaleString())}
					</p>
				)}
				{job.state === 'cancelled' && !scanning && (
					<p className="m-0 text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
						{snapshot ? __('Last scan was cancelled — the previous completed scan is still shown below.', 'ai-rank-suite') : __('Last scan was cancelled; nothing has been published yet.', 'ai-rank-suite')}
					</p>
				)}
				{job.state === 'idle' && (
					<p className="m-0 text-[12.5px]" style={{ color: 'var(--ars-ink-2)' }}>{__('No scan yet — run the first one to see the health of every link on your site.', 'ai-rank-suite')}</p>
				)}
			</Card>
			)}

			{/* The four headline numbers in one bordered grid. Only broken links
			    get an accent — a grid where every cell has a colour says nothing. */}
			{report?.snapshot && totals.total > 0 && (
				<MetricGrid
					columns={4}
					metrics={[
						{
							id: 'total',
							label: __('Links found', 'ai-rank-suite'),
							hint: __('Every link extracted from your published content in the last scan.', 'ai-rank-suite'),
							value: totals.total,
							sub: sprintf(/* translators: 1: internal link count. 2: external link count. */ __('%1$s internal · %2$s external', 'ai-rank-suite'), totals.internal, totals.external),
						},
						{
							id: 'healthy',
							label: __('Healthy', 'ai-rank-suite'),
							hint: __('Responding 2xx — nothing to do here.', 'ai-rank-suite'),
							value: totals.healthy,
						},
						{
							id: 'redirected',
							label: __('Redirected', 'ai-rank-suite'),
							hint: __('Links that only resolve after a redirect hop. Update them to the final URL and save the hop.', 'ai-rank-suite'),
							value: totals.redirected,
						},
						{
							id: 'broken',
							label: __('Broken', 'ai-rank-suite'),
							hint: __('4xx, 5xx or unreachable. A dead link is a trust signal pointing the wrong way.', 'ai-rank-suite'),
							value: totals.broken + totals.unreachable,
							accent: totals.broken + totals.unreachable > 0 ? 'var(--ars-c-red-ink)' : undefined,
						},
					]}
				/>
			)}

			{report?.snapshot && totals.total > 0 && (
				<Card title={__('Composition', 'ai-rank-suite')} subtitle={__('Every checked link by what it answered', 'ai-rank-suite')} flush>
					<div className="px-4 py-3">
						<SegmentBar
							segments={[
								{ label: __('Healthy', 'ai-rank-suite'), value: totals.healthy, color: 'var(--ars-c-green-ink)' },
								{ label: __('Redirected', 'ai-rank-suite'), value: totals.redirected, color: 'var(--ars-accent)' },
								{ label: __('Broken', 'ai-rank-suite'), value: totals.broken, color: 'var(--ars-c-red-ink)' },
								{ label: __('Unreachable', 'ai-rank-suite'), value: totals.unreachable, color: 'var(--ars-ink-3)' },
							]}
						/>
					</div>
				</Card>
			)}

			{(destinations.length > 0 || hosts.length > 0) && (
				<div className={`grid gap-4 items-stretch${destinations.length > 0 && hosts.length > 0 ? ' lg:grid-cols-2' : ''}`}>
					{destinations.length > 0 && (
						<Card
							title={__('Broken destinations', 'ai-rank-suite')}
							subtitle={__('Grouped by target — one fix per row, however many pages point there', 'ai-rank-suite')}
							flush
						>
							<div className="ars-destlist">
								{destinations.map((d) => (
									<div key={`${d.url}|${d.internal}`} className="ars-dest">
										<span className="ars-dest-status">
											<StatusBadge status={d.status} />
										</span>
										<span className="ars-dest-url ars-mono" title={d.url}>
											{d.url}
										</span>
										<span className="ars-dest-pages tabular-nums">
											{d.pages.toLocaleString()} {d.pages === 1 ? 'page' : 'pages'}
											{d.internal ? '' : ' · external'}
										</span>
										<span className="ars-dest-actions">
											{d.internal && (
												<Button variant="tonal" onClick={() => toRedirect(d.url)}>{__('Redirect', 'ai-rank-suite')}</Button>
											)}
											<Button variant="ghost" busy={rechecking === d.url} onClick={() => recheck(d.url)}>{__('Recheck', 'ai-rank-suite')}</Button>
										</span>
									</div>
								))}
							</div>
						</Card>
					)}
					{hosts.length > 0 && (
						<Card title={__('External hosts', 'ai-rank-suite')} subtitle={__('Where your outbound links go, worst first', 'ai-rank-suite')} flush>
							<div className="ars-table-wrap">
								<table className="ars-table">
									<thead>
										<tr>
											<th>{__('Host', 'ai-rank-suite')}</th>
											<th className="is-num">{__('Links', 'ai-rank-suite')}</th>
											<th className="is-num">{__('Redirected', 'ai-rank-suite')}</th>
											<th className="is-num">{__('Broken', 'ai-rank-suite')}</th>
										</tr>
									</thead>
									<tbody>
										{hosts.map((h) => (
											<tr key={h.host}>
												<td className="font-medium">{h.host}</td>
												<td className="is-num">{h.links.toLocaleString()}</td>
												<td className="is-num is-muted">{h.redirected || '—'}</td>
												<td className="is-num" style={h.broken ? { color: 'var(--ars-c-red-ink)', fontWeight: 600 } : undefined}>
													{h.broken || '—'}
												</td>
											</tr>
										))}
									</tbody>
								</table>
							</div>
						</Card>
					)}
				</div>
			)}

			{report?.snapshot && totals.total > 0 && (
				<div ref={resultsRef} className="scroll-mt-4">
				<DataGrid
					query={grid}
					searchPlaceholder={__('Search URL or anchor…', 'ai-rank-suite')}
					toolbar={
						<>
							<ChipBar
								label={__('Show', 'ai-rank-suite')}
								value={grid.state.filters.status || ''}
								onChange={(v) => grid.setFilter('status', v)}
								options={[
									{ id: 'issues', label: __('Issues', 'ai-rank-suite') },
									{ id: 'broken', label: __('Broken', 'ai-rank-suite') },
									{ id: 'redirected', label: __('Redirected', 'ai-rank-suite') },
									{ id: 'healthy', label: __('Healthy', 'ai-rank-suite') },
									{ id: '', label: __('All', 'ai-rank-suite') },
								]}
							/>
							<ChipBar
								label={__('Scope', 'ai-rank-suite')}
								value={grid.state.filters.internal || ''}
								onChange={(v) => grid.setFilter('internal', v)}
								options={[
									{ id: '', label: __('All', 'ai-rank-suite') },
									{ id: 'internal', label: __('Internal', 'ai-rank-suite') },
									{ id: 'external', label: __('External', 'ai-rank-suite') },
								]}
							/>
						</>
					}
					columns={[
						{
							id: 'status',
							label: __('Status', 'ai-rank-suite'),
							sortable: true,
							render: (i) => <StatusBadge status={i.status} />,
						},
						{
							id: 'url',
							label: __('Link', 'ai-rank-suite'),
							sortable: true,
							render: (i) => (
								<div style={{ maxWidth: 360 }}>
									<div className="ars-mono text-[12px] ars-url" title={i.url}>
										{i.url}
									</div>
									<div className="text-[11.5px] mt-0.5" style={{ color: 'var(--ars-ink-3)' }}>
										anchor: “{i.anchor}”
										{i.redirect && (
											<>
												{' '}
												→ <span className="ars-mono">{i.redirect}</span>
											</>
										)}
									</div>
								</div>
							),
						},
						{
							id: 'post_title',
							label: __('Found on', 'ai-rank-suite'),
							render: (i) => (
								<a href={i.edit_url} target="_blank" rel="noreferrer" className="font-medium" style={{ maxWidth: 200, display: 'inline-block' }}>
									{i.post_title || `#${i.post_id}`}
								</a>
							),
						},
					]}
					rowActions={(i) => (
						<div className="ars-actions">
							{i.internal && (i.status >= 400 || i.status === 0) && (
								<Button variant="ghost" onClick={() => toRedirect(i.url)}>{__('Redirect', 'ai-rank-suite')}</Button>
							)}
							<Button variant="ghost" busy={rechecking === i.url} onClick={() => recheck(i.url)}>{__('Recheck', 'ai-rank-suite')}</Button>
						</div>
					)}
					empty={{
						title: __('Nothing in this filter', 'ai-rank-suite'),
						children: __('Every checked link matching this filter is healthy.', 'ai-rank-suite'),
					}}
				/>
				</div>
			)}

			{report?.snapshot && totals.total > 0 && (totals.broken + totals.unreachable + totals.redirected) === 0 && (
				<Notice tone="good">{__('Every checked link is healthy — nothing to fix.', 'ai-rank-suite')}</Notice>
			)}
		</div>
	);
}
