import React, { useCallback, useEffect, useRef, useState } from 'react';
import { __, _n, sprintf } from '@wordpress/i18n';
import { api } from '../api';
import { Card, Button, Badge, Notice, Bar, Switch, PageState, Panel, SectionTitle } from '../components/ui';
import rkSvg from '../icons/vendors/rk.svg?raw';
import aioSvg from '../icons/vendors/aio.svg?raw';
import tsfSvg from '../icons/vendors/seo-framework.svg?raw';
import suSvg from '../icons/vendors/seo-ultimate.svg?raw';
import slimSvg from '../icons/vendors/slim-seo.svg?raw';
import yoastSvg from '../icons/vendors/yoast.svg?raw';
import seopressSvg from '../icons/vendors/seopress.svg?raw';

/**
 * The vendor mark for a migration card. Real artwork where we have it,
 * bundled inline so the admin stays a single file; a tinted initial tile
 * for the rest — never an empty square.
 */
export function VendorIcon({ id, label }) {
	const art = { rankmath: rkSvg, aioseo: aioSvg, seoframework: tsfSvg, seoultimate: suSvg, slimseo: slimSvg, yoast: yoastSvg, seopress: seopressSvg }[id];
	if (art) {
		return (
			<span
				aria-hidden
				className="ars-vendor-icon"
				// Shipped, static artwork from this bundle — not user input.
				dangerouslySetInnerHTML={{ __html: art }}
			/>
		);
	}
	const tint = { yoast: '#a4286a', seopress: '#5b43d6', smartcrawl: '#d63638', wpmetaseo: '#c150ba', squirrly: '#0e7c5a', jetpack: '#069e08', genesis: '#ff5000', woocommerce: '#7f54b3', redirection: '#2271b1' }[id] || 'var(--ars-brand)';
	return (
		<span aria-hidden className="ars-vendor-icon is-initial" style={{ background: tint }}>
			{(label || '?').charAt(0)}
		</span>
	);
}

const num = (n) => Number(n || 0).toLocaleString();
const when = (ts) => (ts ? new Date(ts * 1000).toLocaleString() : '');

/** What a source holds, in one line the reader can check. */
function sourceSummary(s) {
	const parts = [];
	if (s.posts) parts.push(sprintf(_n('%s page', '%s pages', s.posts, 'ai-rank-suite'), num(s.posts)));
	if (s.terms) parts.push(sprintf(_n('%s term', '%s terms', s.terms, 'ai-rank-suite'), num(s.terms)));
	if (s.redirects) parts.push(sprintf(_n('%s redirect', '%s redirects', s.redirects, 'ai-rank-suite'), num(s.redirects)));
	if (s.settings) parts.push(__('site-wide settings', 'ai-rank-suite'));
	return parts.join(' · ');
}

/**
 * The fields a run wrote, named rather than counted — and pluralised, because
 * "1 titles" is the kind of detail that makes a report look machine-made.
 */
function fieldSummary(fields = {}) {
	const label = (key, n) => {
		switch (key) {
			case 'title':
				return sprintf(_n('%s title', '%s titles', n, 'ai-rank-suite'), num(n));
			case 'desc':
				return sprintf(_n('%s description', '%s descriptions', n, 'ai-rank-suite'), num(n));
			case 'keywords':
				return sprintf(_n('%s keyword set', '%s keyword sets', n, 'ai-rank-suite'), num(n));
			case 'canonical':
				return sprintf(_n('%s canonical', '%s canonicals', n, 'ai-rank-suite'), num(n));
			case 'robots':
				return sprintf(_n('%s robots directive', '%s robots directives', n, 'ai-rank-suite'), num(n));
			case 'social':
				return sprintf(_n('%s social card', '%s social cards', n, 'ai-rank-suite'), num(n));
			case 'breadcrumb':
				return sprintf(_n('%s breadcrumb title', '%s breadcrumb titles', n, 'ai-rank-suite'), num(n));
			case 'schema':
				return sprintf(_n('%s schema type', '%s schema types', n, 'ai-rank-suite'), num(n));
			case 'sitemap':
				return sprintf(_n('%s sitemap exclusion', '%s sitemap exclusions', n, 'ai-rank-suite'), num(n));
			case 'term_title':
				return sprintf(_n('%s term title', '%s term titles', n, 'ai-rank-suite'), num(n));
			case 'term_desc':
				return sprintf(_n('%s term description', '%s term descriptions', n, 'ai-rank-suite'), num(n));
			case 'term_canonical':
				return sprintf(_n('%s term canonical', '%s term canonicals', n, 'ai-rank-suite'), num(n));
			case 'term_robots':
				return sprintf(_n('%s term robots directive', '%s term robots directives', n, 'ai-rank-suite'), num(n));
			case 'term_social':
				return sprintf(_n('%s term social card', '%s term social cards', n, 'ai-rank-suite'), num(n));
			case 'redirects':
				return sprintf(_n('%s redirect', '%s redirects', n, 'ai-rank-suite'), num(n));
			case 'settings':
				return sprintf(_n('%s setting', '%s settings', n, 'ai-rank-suite'), num(n));
			default:
				return `${num(n)} ${key}`;
		}
	};
	return Object.entries(fields)
		.filter(([, v]) => v > 0)
		.map(([k, v]) => label(k, v))
		.join(' · ');
}

export default function Import() {
	const [data, setData] = useState(null);
	const [overwrite, setOverwrite] = useState(false);
	const [parts, setParts] = useState({ settings: true, posts: true, terms: true, redirects: true });
	const [job, setJob] = useState(null);
	const [progress, setProgress] = useState(null);
	const [error, setError] = useState(null);
	const [backup, setBackup] = useState(null);
	const [busy, setBusy] = useState(false);
	const [file, setFile] = useState(null);
	const running = useRef(false);
	const fileRef = useRef(null);
	const dataFileRef = useRef(null);

	const load = useCallback(
		() =>
			api
				.get('/import/detect')
				.then((d) => {
					setData(d);
					setJob(d.job && d.job.state === 'running' ? d.job : null);
				})
				.catch((e) => setError(e.message)),
		[]
	);

	useEffect(() => {
		load();
		api.get('/settings/backup').then(setBackup).catch(() => {});
	}, [load]);

	/**
	 * Start a run and tick it to the end. The server does the work in slices;
	 * this loop only keeps asking, so closing the tab pauses rather than
	 * breaks — cron picks the same run up from its cursor.
	 */
	const pump = async (body, label) => {
		if (running.current) return;
		running.current = true;
		setError(null);
		setProgress({ label, counts: {}, done: false });
		try {
			let s = await api.post('/import/start', body);
			setJob(s);
			const carry = (state, done) => ({
				label,
				source: body.source || state.source || '',
				dryRun: !!body.dry_run,
				total: state.total || 0,
				counts: state.counts || {},
				fields: state.fields || {},
				importId: state.import_id,
				errors: state.errors || [],
				done,
			});
			while (s.state === 'running') {
				s = await api.post('/import/tick', {});
				setJob(s.state === 'running' ? s : null);
				setProgress(carry(s, s.state !== 'running'));
				if (s.busy || s.error) await new Promise((r) => setTimeout(r, 1500));
			}
			setProgress(carry(s, true));
		} catch (e) {
			setError(e.message);
		} finally {
			running.current = false;
			setJob(null);
			load();
		}
	};

	const chosenParts = Object.entries(parts)
		.filter(([, on]) => on)
		.map(([k]) => k);

	const runImport = (source, dryRun) =>
		pump(
			{ source, mode: overwrite ? 'overwrite' : 'fill', dry_run: dryRun, parts: chosenParts },
			dryRun ? __('Preview', 'ai-rank-suite') : __('Import', 'ai-rank-suite')
		);

	const undo = (importId) => pump({ undo: importId }, __('Undo', 'ai-rank-suite'));

	const cancel = async () => {
		try {
			await api.post('/import/cancel', {});
		} catch (e) {
			setError(e.message);
		}
	};

	// --- CSV/JSON ---------------------------------------------------------
	const readFile = async (f) => {
		setError(null);
		try {
			const content = await f.text();
			const format = /\.json$/i.test(f.name) ? 'json' : 'csv';
			const parsed = await api.post('/import/preview-file', { content, format });
			if (parsed.error) {
				setError(parsed.error);
				return;
			}
			// Guess the mapping from the column names: an export from this
			// plugin, or a spreadsheet whose headers say what they hold, needs
			// no mapping at all.
			const guess = {};
			(parsed.columns || []).forEach((c) => {
				const k = c.toLowerCase();
				if (/^(seo[_ ]?)?title$/.test(k)) guess[c] = 'title';
				else if (/desc/.test(k)) guess[c] = 'desc';
				else if (/keyword|keyphrase/.test(k)) guess[c] = 'keywords';
				else if (/canonical/.test(k)) guess[c] = 'canonical';
				else if (/robots|noindex/.test(k)) guess[c] = 'robots';
				else if (/og[_ ]?image/.test(k)) guess[c] = 'og_image';
				else if (/og[_ ]?title/.test(k)) guess[c] = 'og_title';
			});
			const match = (parsed.columns || []).includes('id') ? 'id' : (parsed.columns || []).includes('url') ? 'url' : 'slug';
			setFile({ name: f.name, content, format, ...parsed, map: guess, match });
		} catch (e) {
			setError(e.message);
		} finally {
			if (dataFileRef.current) dataFileRef.current.value = '';
		}
	};

	const runFileImport = (dryRun) =>
		pump(
			{
				source: 'file',
				mode: overwrite ? 'overwrite' : 'fill',
				dry_run: dryRun,
				file: { content: file.content, format: file.format, match: file.match, map: file.map },
			},
			dryRun ? __('Preview', 'ai-rank-suite') : __('File import', 'ai-rank-suite')
		);

	// --- export / backup (unchanged behaviour) ----------------------------
	const download = (blob, name) => {
		const a = document.createElement('a');
		a.href = URL.createObjectURL(blob);
		a.download = name;
		a.click();
		URL.revokeObjectURL(a.href);
	};
	const stamp = () => new Date().toISOString().slice(0, 10);

	const exportSettings = async () => {
		setBusy(true);
		try {
			const payload = await api.get('/settings/export');
			download(new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' }), `ai-rank-suite-settings-${stamp()}.json`);
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const exportCsv = async () => {
		setBusy(true);
		try {
			const cols = ['id', 'title', 'url', 'seo_title', 'seo_desc', 'keyword'];
			const rows = [];
			let after = 0;
			let total = null;
			for (let guard = 0; guard < 400; guard++) {
				const r = await api.get(`/content/csv?after=${after}&per_page=500`);
				rows.push(...(r.rows || []));
				total = r.total ?? total;
				setProgress({ exported: rows.length, of: total });
				if (r.next === null || r.next === undefined) break;
				after = r.next;
			}
			// Spreadsheets execute a cell that begins with = + - @ or a tab; a
			// title typed as "=HYPERLINK(...)" must open as text.
			const cell = (v) => {
				let s = String(v ?? '').replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '');
				if (/^[=+\-@\t]/.test(s)) s = "'" + s;
				return `"${s.replace(/"/g, '""')}"`;
			};
			const lines = [cols.join(','), ...rows.map((row) => cols.map((c) => cell(row[c])).join(','))];
			download(new Blob(['\ufeff' + lines.join('\r\n')], { type: 'text/csv;charset=utf-8' }), `ai-rank-suite-seo-data-${stamp()}.csv`);
			setProgress({ exported: rows.length, of: total, done: true });
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

	const importSettings = async (f) => {
		setBusy(true);
		setError(null);
		try {
			const payload = JSON.parse(await f.text());
			const r = await api.post('/settings/import', { payload });
			setProgress({ settings: r.applied });
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
			if (fileRef.current) fileRef.current.value = '';
		}
	};

	const restore = async () => {
		setBusy(true);
		try {
			await api.post('/settings/backup');
			setProgress({ restored: true });
		} catch (e) {
			setError(e.message);
		} finally {
			setBusy(false);
		}
	};

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

	const runs = data.runs || [];
	const conflicts = data.conflicts || {};

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

			{conflicts.emitting && (
				<Notice tone="warning">
					{sprintf(
						/* translators: %s: name of the other SEO plugin. */
						__('%s is still active and writing the meta tags on your pages. Import your data first, check a few pages, then deactivate it — this plugin never switches another one off for you.', 'ai-rank-suite'),
						conflicts.emitting
					)}
				</Notice>
			)}

			<Card
				title={__('Import from another SEO plugin', 'ai-rank-suite')}
				subtitle={__('Titles, descriptions, keywords, canonicals, robots, social cards, term archives, templates and redirects. Nothing in the other plugin is read-only — it is never changed, and every import can be undone in one click.', 'ai-rank-suite')}
				action={<Switch checked={overwrite} onChange={setOverwrite} label={__('Overwrite values we already have', 'ai-rank-suite')} />}
			>
				<div className="flex flex-wrap items-center gap-3 mb-3">
					{[
						['settings', __('Site-wide settings and templates', 'ai-rank-suite')],
						['posts', __('Pages and posts', 'ai-rank-suite')],
						['terms', __('Category and tag archives', 'ai-rank-suite')],
						['redirects', __('Redirects', 'ai-rank-suite')],
					].map(([key, label]) => (
						<Switch key={key} checked={!!parts[key]} onChange={(v) => setParts((p) => ({ ...p, [key]: v }))} label={label} />
					))}
				</div>

				{/* The same progress panel the bulk screen uses — heading, counted
				    subtitle, full-width bar with the percentage and what is left,
				    and the failures listed underneath. A run this long needs a
				    state you can see from across the room, not a grey caption. */}
				{(job || progress) &&
					(() => {
						const counts = (job ? job.counts : progress?.counts) || {};
						const fields = (job ? job.fields : progress?.fields) || {};
						const total = Number(job?.total || progress?.total || 0);
						const seen = Number(counts.seen || 0);
						const failed = Number(counts.errors || 0);
						const kept = Number(counts.kept || 0);
						const running = !!job;
						const pct = total > 0 ? Math.min(running ? 99 : 100, Math.round((seen / total) * 100)) : running ? 0 : 100;
						const remaining = Math.max(0, total - seen);
						const written = fieldSummary(fields);

						return (
							<Card
								className="mb-3"
								title={
									running
										? __('Working…', 'ai-rank-suite')
										: progress?.dryRun
											? __('Preview finished — nothing was written', 'ai-rank-suite')
											: __('Import complete', 'ai-rank-suite')
								}
								subtitle={sprintf(
									/* translators: 1: records processed, 2: records in total. */
									__('%1$s of %2$s records processed', 'ai-rank-suite'),
									Number(seen).toLocaleString(),
									Number(total || seen).toLocaleString()
								) + (failed ? ` · ${sprintf(_n('%s failed', '%s failed', failed, 'ai-rank-suite'), Number(failed).toLocaleString())}` : '')}
								action={
									running ? (
										<Button variant="ghost" onClick={cancel}>{__('Cancel', 'ai-rank-suite')}</Button>
									) : (
										<Badge tone={failed ? 'warning' : 'good'}>
											{failed
												? sprintf(_n('%s failed', '%s failed', failed, 'ai-rank-suite'), Number(failed).toLocaleString())
												: __('Complete', 'ai-rank-suite')}
										</Badge>
									)
								}
							>
								<div className="grid gap-2">
									<Bar value={pct} />
									<div className="flex justify-between text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
										<span>{pct}%</span>
										<span>
											{running
												? sprintf(
														/* translators: %s: number of records still to process. */
														__('%s remaining', 'ai-rank-suite'),
														Number(remaining).toLocaleString()
													)
												: written || __('Nothing needed changing.', 'ai-rank-suite')}
										</span>
									</div>
									{!running && written && (
										<div className="text-[13px]" style={{ color: 'var(--ars-ink)' }}>{written}</div>
									)}
									{!running && kept > 0 && (
										<div className="flex flex-wrap items-center gap-2">
											<span className="text-[12.5px]" style={{ color: 'var(--ars-ink-2)' }}>
												{sprintf(
													_n(
														'%s value was left alone because this plugin already holds one for that field.',
														'%s values were left alone because this plugin already holds one for those fields.',
														kept,
														'ai-rank-suite'
													),
													Number(kept).toLocaleString()
												)}
											</span>
											<Button
												onClick={() => {
													setOverwrite(true);
													pump(
														{ source: progress?.source || '', mode: 'overwrite', dry_run: false, parts: chosenParts },
														__('Import', 'ai-rank-suite')
													);
												}}
											>
												{__('Import again and overwrite them', 'ai-rank-suite')}
											</Button>
										</div>
									)}
									{!running && counts.unmatched ? (
										<div className="text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
											{sprintf(
												_n('%s row could not be matched to a page.', '%s rows could not be matched to a page.', counts.unmatched, 'ai-rank-suite'),
												Number(counts.unmatched).toLocaleString()
											)}
										</div>
									) : null}
									{(progress?.errors || []).length > 0 && (
										<ul className="m-0 mt-1 pl-5 text-[12px]" style={{ color: 'var(--ars-serious)' }}>
											{progress.errors.slice(0, 8).map((e, i) => (
												<li key={i}>
													<code>{e.what}</code> — {e.message}
												</li>
											))}
										</ul>
									)}
									{!running && progress?.importId && !progress?.dryRun && (
										<div className="flex items-center gap-2 mt-1">
											<Button variant="ghost" onClick={() => undo(progress.importId)}>
												{__('Undo this import', 'ai-rank-suite')}
											</Button>
											<span className="t-caption">{__('Run the content analysis afterwards so the scores use the imported data.', 'ai-rank-suite')}</span>
										</div>
									)}
								</div>
							</Card>
						);
					})()}

				{/*
				 * Two lists, not one grid of nine identical cards.
				 *
				 * The plugin that is installed, or has left data behind, is the
				 * reason somebody opened this screen; it gets a card with the
				 * numbers and the buttons. The seven that are not on this site
				 * are a fact, not a choice, and a fact fits on one line each.
				 */}
				{(data.sources || []).some((s) => !!sourceSummary(s)) ? null : (
					<p className="m-0 mb-3 text-[13px]" style={{ color: 'var(--ars-ink-2)' }}>
						{__('No other SEO plugin, and no data left behind by one, was found on this site. When one is installed its card appears here with what it holds.', 'ai-rank-suite')}
					</p>
				)}
				<div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))' }}>
					{(data.sources || []).filter((s) => !!sourceSummary(s)).map((s) => {
						const summary = sourceSummary(s);
						const nothing = !summary;
						return (
							<div key={s.id} className="ars-vendor-card">
								<VendorIcon id={s.id} label={s.label} />
								<div className="font-semibold text-[13.5px] flex items-center justify-center gap-2">
									{s.label}
									{s.active && <Badge tone="brand">{__('Active', 'ai-rank-suite')}</Badge>}
								</div>
								<div className="text-[12px] text-center" style={{ color: 'var(--ars-ink-2)' }}>
									{nothing
										? s.active
											? __('Active, but nothing is stored here to import — this plugin only saves values somebody typed by hand.', 'ai-rank-suite')
											: __('Supported. Nothing from it on this site — no plugin, no leftover data.', 'ai-rank-suite')
										: summary}
								</div>
								<div className="flex items-center justify-center gap-2">
									<Button variant={nothing ? 'ghost' : 'primary'} disabled={nothing || !!job} onClick={() => runImport(s.id, false)}>
										{nothing ? __('Nothing to import', 'ai-rank-suite') : __('Import', 'ai-rank-suite')}
									</Button>
									{!nothing && (
										<Button variant="ghost" disabled={!!job} onClick={() => runImport(s.id, true)} title={__('Run without writing anything', 'ai-rank-suite')}>
											{__('Preview', 'ai-rank-suite')}
										</Button>
									)}
								</div>
							</div>
						);
					})}
				</div>

				{(data.sources || []).some((s) => !sourceSummary(s)) && (
					<div className="mt-3">
						<div className="text-[11px] font-semibold uppercase tracking-wide mb-2" style={{ color: 'var(--ars-ink-3)' }}>{__('Also supported, nothing from them on this site', 'ai-rank-suite')}</div>
						<div className="flex flex-wrap gap-2">
							{(data.sources || []).filter((s) => !sourceSummary(s)).map((s) => (
								<span key={s.id} className="inline-flex items-center gap-2 pr-3 pl-1 py-1 ars-r-md" style={{ border: '1px solid var(--ars-border)', background: 'var(--ars-surface)' }} title={s.active ? __('Active, but nothing is stored here to import.', 'ai-rank-suite') : __('Supported. Nothing from it on this site.', 'ai-rank-suite')}>
									<span className="ars-vendor-icon is-small"><VendorIcon id={s.id} label={s.label} /></span>
									<span className="text-[12.5px]">{s.label}</span>
									{s.active && <Badge tone="brand">{__('Active', 'ai-rank-suite')}</Badge>}
								</span>
							))}
						</div>
					</div>
				)}

				<p className="m-0 mt-3 text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
					{__('Template variables such as %%title%% are translated into this plugin’s own. A variable nothing here can resolve is dropped rather than printed into your pages. Run the content analysis afterwards so the scores reflect the imported data.', 'ai-rank-suite')}
				</p>
			</Card>

			<Card
				title={__('Import a spreadsheet', 'ai-rank-suite')}
				subtitle={__('A CSV or JSON file with one row per page — titles and descriptions written elsewhere, or an export from this plugin edited and brought back.', 'ai-rank-suite')}
			>
				<div className="flex flex-wrap items-center gap-2">
					<Button variant="ghost" onClick={() => dataFileRef.current?.click()} disabled={!!job}>
						{file ? file.name : __('Choose a CSV or JSON file', 'ai-rank-suite')}
					</Button>
					<input
						ref={dataFileRef}
						type="file"
						accept=".csv,.json,text/csv,application/json"
						style={{ display: 'none' }}
						onChange={(e) => e.target.files?.[0] && readFile(e.target.files[0])}
					/>
					{file && (
						<Badge tone="neutral">
							{sprintf(_n('%s row', '%s rows', file.rows, 'ai-rank-suite'), num(file.rows))}
						</Badge>
					)}
				</div>

				{file && (
					<div className="mt-3 grid gap-3">
						<div className="flex flex-wrap items-center gap-2 text-[12.5px]">
							<span>{__('Match rows to pages by', 'ai-rank-suite')}</span>
							<select style={{ width: 'auto' }} value={file.match} onChange={(e) => setFile({ ...file, match: e.target.value })}>
								{(file.columns || []).map((c) => (
									<option key={c} value={c}>
										{c}
									</option>
								))}
							</select>
						</div>
						<SectionTitle hint={__('Every column you map is written; the rest are ignored.', 'ai-rank-suite')}>{__('Columns', 'ai-rank-suite')}</SectionTitle>
						<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))' }}>
							{(file.columns || []).map((c) => (
								<label key={c} className="flex items-center justify-between gap-2 text-[12.5px]">
									<code>{c}</code>
									<select
										className="ars-input"
										style={{ width: 'auto' }}
										value={file.map[c] || ''}
										onChange={(e) => setFile({ ...file, map: { ...file.map, [c]: e.target.value } })}
									>
										<option value="">{__('— ignore —', 'ai-rank-suite')}</option>
										{Object.entries(data.fields || {}).map(([key, label]) => (
											<option key={key} value={key}>
												{label}
											</option>
										))}
									</select>
								</label>
							))}
						</div>
						<div className="flex items-center gap-2">
							<Button disabled={!!job} onClick={() => runFileImport(false)}>
								{__('Import the file', 'ai-rank-suite')}
							</Button>
							<Button variant="ghost" disabled={!!job} onClick={() => runFileImport(true)}>
								{__('Preview', 'ai-rank-suite')}
							</Button>
						</div>
					</div>
				)}
			</Card>

			{runs.length > 0 && (
				<Card title={__('Import history', 'ai-rank-suite')} subtitle={__('Every run, what it changed, and a way back.', 'ai-rank-suite')}>
					<div className="grid gap-2">
						{runs.map((r) => (
							<div key={r.import_id} className="flex flex-wrap items-center justify-between gap-2 py-2" style={{ borderBottom: '1px solid var(--ars-line)' }}>
								<div className="text-[12.5px]">
									<div className="font-semibold">
										{r.source === 'file' ? __('Spreadsheet', 'ai-rank-suite') : (data.sources || []).find((s) => s.id === r.source)?.label || r.source}
										{r.dry_run ? ` · ${__('preview only', 'ai-rank-suite')}` : ''}
										{r.mode === 'overwrite' ? ` · ${__('overwrite', 'ai-rank-suite')}` : ''}
									</div>
									<div style={{ color: 'var(--ars-ink-2)' }}>
										{when(r.started)} · {fieldSummary(r.fields) || __('nothing changed', 'ai-rank-suite')}
										{r.counts?.errors ? ` · ${sprintf(_n('%s error', '%s errors', r.counts.errors, 'ai-rank-suite'), num(r.counts.errors))}` : ''}
									</div>
									{Array.isArray(r.errors) && r.errors.length > 0 && (
										<Panel title={__('What failed', 'ai-rank-suite')}>
											<ul className="m-0 pl-4 text-[12px]">
												{r.errors.map((e, i) => (
													<li key={i}>
														<code>{e.what}</code> — {e.message}
													</li>
												))}
											</ul>
										</Panel>
									)}
								</div>
								<div className="flex items-center gap-2">
									{r.undone ? (
										<Badge tone="neutral">{__('Undone', 'ai-rank-suite')}</Badge>
									) : r.undoable ? (
										<Button variant="ghost" disabled={!!job} onClick={() => undo(r.import_id)}>
											{__('Undo this import', 'ai-rank-suite')}
										</Button>
									) : (
										<Badge tone="neutral">{__('Preview', 'ai-rank-suite')}</Badge>
									)}
								</div>
							</div>
						))}
					</div>
				</Card>
			)}

			<Card title={__('Export & backup', 'ai-rank-suite')} subtitle={__('Take your configuration and your SEO data with you — to another site, a spreadsheet, or a rollback', 'ai-rank-suite')}>
				<div className="flex flex-wrap items-center gap-2">
					<Button onClick={exportCsv} busy={busy}>
						{progress?.exported !== undefined && !progress.done
							? sprintf(__('Exporting… %s', 'ai-rank-suite'), num(progress.exported))
							: __('Export SEO data (CSV)', 'ai-rank-suite')}
					</Button>
					{progress?.done && progress?.exported !== undefined && (
						<Badge tone="good">{sprintf(__('%s rows exported', 'ai-rank-suite'), num(progress.exported))}</Badge>
					)}
					<Button variant="ghost" onClick={exportSettings} busy={busy}>{__('Export settings (JSON)', 'ai-rank-suite')}</Button>
					<Button variant="ghost" onClick={() => fileRef.current?.click()} busy={busy}>{__('Import settings file', 'ai-rank-suite')}</Button>
					<input
						ref={fileRef}
						type="file"
						accept="application/json,.json"
						style={{ display: 'none' }}
						onChange={(e) => e.target.files?.[0] && importSettings(e.target.files[0])}
					/>
					{backup?.exists && (
						<Button variant="ghost" onClick={restore} busy={busy}>{__('Restore pre-update backup', 'ai-rank-suite')}</Button>
					)}
					{progress?.settings !== undefined && <Badge tone="good">{sprintf(__('%s settings applied', 'ai-rank-suite'), num(progress.settings))}</Badge>}
					{progress?.restored && <Badge tone="good">{__('Restored', 'ai-rank-suite')}</Badge>}
				</div>
				<p className="m-0 mt-3 text-[12px]" style={{ color: 'var(--ars-ink-3)' }}>
					{__('API keys and license data are never included in an export.', 'ai-rank-suite')}
					{backup?.exists && ` ${sprintf(__('A backup from version %s was taken automatically before the last update.', 'ai-rank-suite'), backup.from)}`}
				</p>
			</Card>
		</div>
	);
}
