import React, { useEffect, useMemo, useRef, useState } from 'react';
import { __, _n, sprintf } from '@wordpress/i18n';

/**
 * The operations floor.
 *
 * A wiring diagram of the plugin: the circuits along the top, the engines
 * feeding the bus, the desks they staff below, and the clockwork ticking on
 * the right. Nothing here is decorative in the sense of being made up — every
 * node is a stored credential, every wire is a row in the routing table, every
 * gear is a real WordPress cron entry with a real next-run time.
 *
 * The motion rule is the whole discipline of this component: **it only moves
 * when something is actually happening.**
 *
 * - The bus carries current when an engine can answer. No key, no current.
 * - A desk lights up only while the agent reports work routed to it.
 * - A gear turns only while its job is scheduled and not overdue.
 *
 * A dashboard that animates identically whether or not anything is running
 * teaches people to stop looking at it, and then it is worse than a static
 * table. An idle plugin should look idle. That is why the dark state has as
 * much design in it as the live one.
 */

/*
 * The desks, keyed on something that does not change when the language does.
 *
 * These were matched against the desk *name* the server sent, which is a
 * translated string: on a German site the router said "Textredaktion" and this
 * list said "Copy desk", nothing matched, and every desk silently vanished —
 * the whole point of the panel, missing, with no error anywhere. The server now
 * sends a stable `desk_id` beside the translated label, and the label is used
 * only for reading.
 */
const DESKS = [
	{ id: 'copy', label: __('Copy desk', 'ai-rank-suite'), hint: __('Writes', 'ai-rank-suite') },
	{ id: 'research', label: __('Research', 'ai-rank-suite'), hint: __('Finds', 'ai-rank-suite') },
	{ id: 'technical', label: __('Technical', 'ai-rank-suite'), hint: __('Structures', 'ai-rank-suite') },
	{ id: 'analysis', label: __('Analysis', 'ai-rank-suite'), hint: __('Measures', 'ai-rank-suite') },
	{ id: 'operations', label: __('Operations', 'ai-rank-suite'), hint: __('Runs at scale', 'ai-rank-suite') },
];

/** The stable key for a job's desk, with the old translated name as a fallback. */
const deskKey = (job) => job?.desk_id || job?.desk || '';

/**
 * @param {boolean} compact  Drop the supply rail and the clockwork column and
 *                           keep the part that shows the AI at work: the bus,
 *                           the engines feeding it and the desks they staff.
 *                           The dashboard wants "is it running", not the whole
 *                           wiring diagram — that stays on the Autopilot screen.
 */
// Every array below is read defensively. This component is fed straight from
// /autopilot/status, it is now mounted on the dashboard as well as the
// autopilot screen, and a payload missing one key would take both down — the
// same failure BandScale had, in the component with the widest blast radius.
export default function AgencyFloor({ routing, systems, now, running, compact = false }) {
	const engines = routing?.engines || [];
	const jobs = routing?.jobs || [];
	const live = engines.length > 0;
	const reduce = usePrefersReducedMotion();

	const desks = useMemo(() => {
		const byDesk = new Map();
		jobs.forEach((j) => {
			const key = deskKey(j);
			if (!byDesk.has(key)) byDesk.set(key, []);
			byDesk.get(key).push(j);
		});
		// Known desks in their intended order first, then anything the server
		// adds later — a new desk should appear, not disappear.
		const known = DESKS.filter((d) => byDesk.has(d.id)).map((d) => ({ ...d, jobs: byDesk.get(d.id) }));
		const extra = [...byDesk.keys()]
			.filter((k) => k && !DESKS.some((d) => d.id === k))
			.map((k) => ({ id: k, label: byDesk.get(k)[0]?.desk || k, hint: '', jobs: byDesk.get(k) }));
		return [...known, ...extra];
	}, [jobs]);

	const activeDesk = useMemo(() => {
		if (!now) return null;
		const job = jobs.find((j) => j.id === jobFor(now.task));
		return job ? deskKey(job) : null;
	}, [now, jobs]);

	const engineIndex = useMemo(() => {
		const map = new Map();
		engines.forEach((e, i) => map.set(e.id, i));
		return map;
	}, [engines]);

	const circuits = systems?.circuits || [];
	const clock = systems?.clockwork || null;
	/*
	 * How many kinds of work exist, from the server rather than from the length
	 * of whichever array this component happens to be holding. The load bar
	 * used to divide by `jobs.length`, which is the same number only for as
	 * long as nobody filters that list.
	 */
	const jobsTotal = Number(routing?.jobs_total) || jobs.length || 1;

	return (
		<div className={`ars-floor${live ? ' is-live' : ''}${reduce ? ' is-still' : ''}${compact ? ' is-compact' : ''}`}>
			{/* ── What it is plugged into, hanging off the artery ─────────── */}
			{circuits.length > 0 && (
				<div className="ars-circuits-wrap">
					<div className="ars-floor-rail-label">
						<span className="t-overline">{__('Connected to', 'ai-rank-suite')}</span>
						<span className="t-caption">
							{sprintf(
								/* translators: 1: connected count, 2: total. */
								__('%1$s of %2$s services are plugged in. Each one below says what stops working while it is dark.', 'ai-rank-suite'),
								circuits.filter((c) => c.live).length,
								circuits.length
							)}
						</span>
					</div>

					{/*
					 * The artery.
					 *
					 * One moving line with a stub dropping into every service
					 * below it, so the picture reads as current going somewhere
					 * rather than as a loading bar. It moves only while an
					 * engine can answer: no key, no current, and the whole row
					 * goes still. That rule is the reason the animation is worth
					 * having at all.
					 */}
					<div className="ars-vein" aria-hidden>
						<span className="ars-vein-flow" />
					</div>

					<div className="ars-circuits">
						{circuits.map((c) => (
							<button
								key={c.id}
								type="button"
								className={`ars-circuit${c.live ? ' is-live' : ''}${c.core ? ' is-core' : ''}`}
								title={c.powers}
								onClick={() => {
									if (c.href) window.location.hash = `/${c.href}`;
								}}
							>
								<span className="ars-circuit-stem" aria-hidden />
								<span className="ars-circuit-led" aria-hidden />
								<span className="ars-circuit-name">{c.label}</span>
								<span className="ars-circuit-detail">{c.detail}</span>
								<span className="ars-circuit-powers">{c.powers}</span>
							</button>
						))}
					</div>
				</div>
			)}

			{!live && (
				<>
					<div className="ars-floor-off">
						<span className="ars-floor-off-mark" aria-hidden />
						<div className="min-w-0">
							<div className="font-semibold text-[13px]">{__('The floor is dark', 'ai-rank-suite')}</div>
							<p className="m-0 mt-1 text-[12.5px]">
								{__('No API key is stored, so nothing downstream can run. Connect one engine and every desk below wakes up at once — the assignments already exist, they are just unpowered.', 'ai-rank-suite')}
							</p>
						</div>
						<button
							type="button"
							className="ars-btn ars-btn-primary"
							onClick={() => {
								window.location.hash = '/ai';
							}}
						>{__('Connect an engine', 'ai-rank-suite')}</button>
					</div>

					{/*
					 * The unpowered floor, drawn anyway.
					 *
					 * The desks and their assignments exist whether or not a key
					 * does — they are rows in the routing table. Showing them
					 * greyed is the difference between "this plugin does nothing"
					 * and "this plugin is wired and waiting", and it is the same
					 * diagram that lights up, so nothing moves when the key
					 * arrives; it only gains colour.
					 */}
					{/*
					 * The clockwork runs whether or not an engine is connected —
					 * a rank snapshot and a backlink re-check need no model. It
					 * was drawn only in the live branch, so on the screen most
					 * people see first the two overdue jobs were invisible: the
					 * one thing here that is genuinely broken, hidden behind the
					 * one thing that is merely unconfigured.
					 */}
					{! compact && clock && <Clockwork clock={clock} still={reduce} />}

					{desks.length > 0 && (
						<>
						<div className="ars-floor-rail-label">
							<span className="t-overline">{__('Would be done with AI', 'ai-rank-suite')}</span>
							<span className="t-caption">
								{sprintf(
									/* translators: 1: number of job types. */
									__('%s kinds of work are wired up and waiting for a key. Nothing here is running.', 'ai-rank-suite'),
									jobsTotal
								)}
							</span>
						</div>
						<div className="ars-floor-desks is-dark">
							{desks.map((d) => (
								<div key={d.id} className="ars-desk is-dark">
									<div className="ars-desk-head">
										<span className="ars-desk-name">{d.label || d.id}</span>
										<span className="ars-desk-hint">{d.hint}</span>
									</div>
									<ul className="ars-desk-jobs">
										{(d.jobs || []).map((j) => (
											<li key={j.id}>
												<span className="ars-desk-job">{j.label}</span>
												<span className="ars-desk-eng is-cold">{__('unpowered', 'ai-rank-suite')}</span>
											</li>
										))}
									</ul>
								</div>
							))}
						</div>
						</>
					)}
				</>
			)}

			{live && (
				<>
					<div className="ars-floor-engines">
						{engines.map((e) => {
							const busy = now && (e.jobs || []).some((j) => j.id === jobFor(now.task));
							return (
								<div key={e.id} className={`ars-eng${busy ? ' is-busy' : ''}${e.default ? ' is-default' : ''}`}>
									<span className="ars-eng-lead" aria-hidden />
									<div className="ars-eng-head">
										<span className="ars-eng-dot" aria-hidden />
										<span className="ars-eng-name">{e.label}</span>
										{e.default && <span className="ars-eng-flag">{__('default', 'ai-rank-suite')}</span>}
									</div>
									<div className="ars-eng-model">{e.model}</div>
									<div
										className="ars-eng-load"
										title={sprintf(
											/* translators: 1: jobs routed here, 2: jobs in total. */
											__('%1$s of the %2$s kinds of work on this site are routed to this engine. It counts assignments, not calls made and not money spent.', 'ai-rank-suite'),
											e.load,
											jobsTotal
										)}
									>
										<div className="ars-eng-load-bar">
											<span style={{ width: `${Math.min(100, (e.load / Math.max(1, jobsTotal)) * 100)}%` }} />
										</div>
										<span className="tabular-nums">
											{sprintf(
												/* translators: 1: jobs routed here, 2: jobs in total. */
												__('%1$s of %2$s job types', 'ai-rank-suite'),
												e.load,
												jobsTotal
											)}
										</span>
									</div>
								</div>
							);
						})}
					</div>

					<div className="ars-floor-rail-label">
						<span className="t-overline">{__('Done with AI', 'ai-rank-suite')}</span>
						<span className="t-caption">
							{sprintf(
								/* translators: 1: number of job types. */
								__('%s kinds of work, each one sent to the engine named beside it. A line lights up only while that work is actually running.', 'ai-rank-suite'),
								jobsTotal
							)}
						</span>
					</div>

					<div className="ars-floor-body">
						<div className="ars-floor-desks">
							{desks.map((d) => {
								const isActive = activeDesk === d.id;
								return (
									<div key={d.id} className={`ars-desk${isActive ? ' is-active' : ''}`}>
										<div className="ars-desk-head">
											<span className="ars-desk-name">{d.label || d.id}</span>
											<span className="ars-desk-hint">{d.hint}</span>
										</div>
										<ul className="ars-desk-jobs">
											{(d.jobs || []).map((j) => {
												const onNow = now && jobFor(now.task) === j.id;
												return (
													<li key={j.id} className={onNow ? 'is-on' : ''}>
														<span className="ars-desk-job">{j.label}</span>
														<span
															className={`ars-desk-eng${j.ready ? '' : ' is-cold'}`}
															title={
																j.fell_back
																	? __('Assigned elsewhere, but that engine has no key — this is where the work is really going.', 'ai-rank-suite')
																	: j.assigned
																		? __('Assigned explicitly', 'ai-rank-suite')
																		: __('Uses the default engine', 'ai-rank-suite')
															}
															style={
																engineIndex.has(j.provider)
																	? { '--ars-wire': wireColor(engineIndex.get(j.provider)) }
																	: undefined
															}
														>
															{j.fell_back ? '↳ ' : ''}
															{shorten(engines.find((e) => e.id === j.provider)?.label || j.provider)}
														</span>
													</li>
												);
											})}
										</ul>
									</div>
								);
							})}
						</div>

						{! compact && clock && <Clockwork clock={clock} still={reduce} />}
					</div>
				</>
			)}

			{/* ── The live line ───────────────────────────────────────────── */}
			<div className={`ars-floor-now${now ? ' is-on' : ''}`} aria-live="polite">
				{now ? (
					<>
						<span className="ars-floor-now-mark" aria-hidden />
						<span className="ars-floor-now-text">
							<b>{labelFor(now.task)}</b> {sprintf(__('for “%1$s” — %2$s', 'ai-rank-suite'), now.title, now.engine)}
						</span>
						<Elapsed since={now.since} />
					</>
				) : (
					<span className="ars-floor-now-text is-idle">
						{running ? __('Between tasks…', 'ai-rank-suite') : live ? __('Powered. Nothing running right now.', 'ai-rank-suite') : __('Idle. Nothing is running.', 'ai-rank-suite')}
					</span>
				)}
			</div>
		</div>
	);
}

/**
 * The scheduled machinery.
 *
 * A gear turns only while its job is scheduled and on time. The honest part is
 * the note underneath: WordPress cron fires on page views, not on a timer, so
 * "daily" on a quiet site means "whenever somebody next turns up" — and with
 * DISABLE_WP_CRON set and no system cron behind it, none of this has ever run.
 * That failure is completely silent in WordPress, which is exactly why it gets
 * a sentence here instead of a green dot.
 */
function Clockwork({ clock, still }) {
	return (
		<div className="ars-clock">
			<div className="ars-clock-head">
				<span className="ars-clock-title">{__('Clockwork', 'ai-rank-suite')}</span>
				{clock.overdue > 0 ? (
					<span className="ars-clock-flag is-bad">{sprintf(__('%d overdue', 'ai-rank-suite'), clock.overdue)}</span>
				) : clock.disabled ? (
					<span className="ars-clock-flag is-warn">{__('cron handed off', 'ai-rank-suite')}</span>
				) : (
					<span className="ars-clock-flag">{__('on time', 'ai-rank-suite')}</span>
				)}
			</div>

			<ul className="ars-clock-gears">
				{(clock.gears || []).map((g) => (
					<li key={g.hook} className={`ars-gear${g.late ? ' is-late' : ''}${!g.scheduled ? ' is-off' : ''}`}>
						<span
							className={`ars-gear-wheel${g.scheduled && !g.late && !still ? ' is-turning' : ''}`}
							aria-hidden
						/>
						<span className="min-w-0">
							<span className="ars-gear-name" title={g.about}>
								{g.label}
							</span>
							<span className="ars-gear-when">
								{!g.scheduled
									? __('not scheduled', 'ai-rank-suite')
									: g.late
										? sprintf(__('overdue by %s', 'ai-rank-suite'), humanise(-g.in))
										: sprintf(__('in %s', 'ai-rank-suite'), humanise(g.in))}
								{g.last > 0 && ` · ${sprintf(__('last ran %s', 'ai-rank-suite'), ago(g.last))}`}
								{g.last === 0 && g.scheduled && ` · ${__('never run yet', 'ai-rank-suite')}`}
							</span>
						</span>
					</li>
				))}
			</ul>

			<p className="ars-clock-note">{clock.note}</p>
		</div>
	);
}

/** Seconds since the agent picked up the current task. */
function Elapsed({ since }) {
	const [now, setNow] = useState(() => Math.floor(Date.now() / 1000));
	useEffect(() => {
		const id = setInterval(() => setNow(Math.floor(Date.now() / 1000)), 1000);
		return () => clearInterval(id);
	}, []);
	// The server clock and the browser clock disagree by a few seconds on most
	// hosts; clamping at zero keeps that from showing as a negative timer.
	const secs = Math.max(0, now - (since || now));
	return <span className="ars-floor-now-time tabular-nums">{secs}s</span>;
}

function usePrefersReducedMotion() {
	const [reduce, setReduce] = useState(false);
	const mq = useRef(null);
	useEffect(() => {
		mq.current = window.matchMedia?.('(prefers-reduced-motion: reduce)');
		if (!mq.current) return undefined;
		const sync = () => setReduce(mq.current.matches);
		sync();
		mq.current.addEventListener?.('change', sync);
		return () => mq.current?.removeEventListener?.('change', sync);
	}, []);
	return reduce;
}

/** "in 4h", "in 3 days" — deliberately coarse, because the number is coarse. */
function humanise(seconds) {
	const s = Math.max(0, seconds);
	if (s < 60) return __('under a minute', 'ai-rank-suite');
	if (s < 3600) return sprintf(__('%d min', 'ai-rank-suite'), Math.round(s / 60));
	if (s < 86400) return sprintf(__('%dh', 'ai-rank-suite'), Math.round(s / 3600));
	const d = Math.round(s / 86400);
	return sprintf(_n('%d day', '%d days', d, 'ai-rank-suite'), d);
}

function ago(ts) {
	return sprintf(__('%s ago', 'ai-rank-suite'), humanise(Math.floor(Date.now() / 1000) - ts));
}

const TASK_JOBS = { description: 'meta', title: 'meta', faq: 'faq' };
const TASK_LABELS = {
	description: __('Writing a meta description', 'ai-rank-suite'),
	title: __('Writing an SEO title', 'ai-rank-suite'),
	faq: __('Generating questions', 'ai-rank-suite'),
};

function jobFor(task) {
	return TASK_JOBS[task] || 'autopilot';
}
function labelFor(task) {
	return TASK_LABELS[task] || task;
}
function shorten(label) {
	if (!label) return '';
	return label.length > 22 ? `${label.slice(0, 21)}…` : label;
}
function wireColor(i) {
	const wires = [
		'var(--ars-brand)',
		'var(--ars-accent)',
		'var(--ars-c-green-ink)',
		'var(--ars-c-blue-ink)',
		'var(--ars-c-violet-ink)',
	];
	return wires[i % wires.length];
}
