import { useRef } from 'react';
import { MapPin } from 'lucide-react';

/**
 * ChangeDetails
 *
 * Renders the "Technical changes" of a timeline action. The executor flattens
 * `result` per handler, so each known handler gets a tailored render (selected
 * by `task.handler` = `l3.context.handler`). Unknown handlers fall back to a
 * generic before/after split diff, and entries without before/after (e.g. the
 * fabricated initial-setup) fall back to the legacy string list.
 *
 * Identifiers (page_path, wp_post_id, builder…) always come from `task.context`,
 * never from before/after. `task.detail` is display-only / opaque.
 */

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const hasKeys = (obj) => obj && Object.keys(obj).length > 0;

const truncate = (str, max = 240) =>
	str.length > max ? `${str.slice(0, max)}…` : str;

const fileName = (url) => {
	if (typeof url !== 'string') return '';
	const clean = url.split('?')[0].split('#')[0];
	return clean.split('/').filter(Boolean).pop() || url;
};

/** Collapse whitespace and clip to a short excerpt (a couple of lines). */
const clip = (str, max = 160) => {
	if (typeof str !== 'string') return '';
	const collapsed = str.replace(/\s+/g, ' ').trim();
	return collapsed.length > max ? `${collapsed.slice(0, max)}…` : collapsed;
};

const EMPTY = '∅';

// ---------------------------------------------------------------------------
// Shared presentational pieces
// ---------------------------------------------------------------------------

/** One row of a field diff. */
const DiffRow = ({ children, tone, sign, wrapCls }) => (
	<div className={`flex gap-2 px-3 py-1.5 font-mono text-xs ${tone}`}>
		<span className="select-none opacity-50">{sign}</span>
		<div className={`min-w-0 flex-1 ${wrapCls}`}>{children}</div>
	</div>
);

/** A single field rendered as a before/after (unified-diff) pair. */
const FieldDiff = ({ label, before, after, frame, multiline }) => {
	const render = (v) => (frame ? frame(v) : v === '' || v == null ? EMPTY : v);
	const wrapCls = multiline
		? 'whitespace-pre-wrap break-words max-h-60 overflow-y-auto'
		: 'whitespace-nowrap overflow-x-auto';
	const unchanged = before === after;

	return (
		<div>
			{label && (
				<p className="small-medium text-muted-foreground mb-1.5 m-0!">
					{label}
				</p>
			)}
			<div className="rounded-md overflow-hidden border border-border">
				{unchanged ? (
					<DiffRow
						tone="bg-white text-foreground"
						sign=" "
						wrapCls={wrapCls}
					>
						{render(after)}
					</DiffRow>
				) : (
					<>
						<DiffRow
							tone="bg-rose-50 text-rose-700"
							sign="-"
							wrapCls={wrapCls}
						>
							{render(before)}
						</DiffRow>
						<DiffRow
							tone="bg-emerald-50 text-emerald-700"
							sign="+"
							wrapCls={wrapCls}
						>
							{render(after)}
						</DiffRow>
					</>
				)}
			</div>
		</div>
	);
};

/** A simple status card (created / verified / added …). */
const StatusCard = ({ title, hint }) => (
	<div className="rounded-md border border-border bg-white p-3">
		<p className="small-medium text-foreground m-0!">{title}</p>
		{hint && (
			<p className="font-mono text-xs text-muted-foreground truncate mt-0.5 m-0!">
				{hint}
			</p>
		)}
	</div>
);

/** Technical context tags (applied_via, builder…) shown under a change. */
const ContextMeta = ({ context }) => {
	const items = [
		['applied via', context?.applied_via],
		['builder', context?.builder],
	].filter(([, v]) => v != null && v !== '');
	if (!items.length) return null;
	return (
		<div className="flex flex-wrap gap-2">
			{items.map(([label, value]) => (
				<span
					key={label}
					className="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-neutral-100 text-xs"
				>
					<span className="text-muted-foreground">{label}:</span>
					<span className="font-mono text-foreground">{value}</span>
				</span>
			))}
		</div>
	);
};

/** Render lines as added (+) diff lines in a dark code block. */
const AddedLines = ({ lines }) => (
	<div className="overflow-auto max-h-72 rounded-md bg-neutral-900 font-mono text-xs leading-6">
		{lines.map((ln, i) => (
			<div
				key={i}
				className="min-w-full w-max whitespace-pre px-3 bg-emerald-500/15 text-emerald-200"
			>
				<span className="select-none opacity-40 mr-3">+</span>
				{ln}
			</div>
		))}
	</div>
);

// ---------------------------------------------------------------------------
// Generic split diff (fallback)
// ---------------------------------------------------------------------------

const buildDiffRows = (before, after) => {
	const b = before || {};
	const a = after || {};
	const keys = [...Object.keys(a), ...Object.keys(b).filter((k) => !(k in a))];
	const line = (k, v) => `  "${k}": ${truncate(JSON.stringify(v) ?? 'null')}`;
	const rows = [{ type: 'context', left: '{', right: '{' }];
	keys.forEach((k) => {
		const inB = k in b;
		const inA = k in a;
		if (inB && inA) {
			const left = line(k, b[k]);
			const right = line(k, a[k]);
			rows.push({ type: left === right ? 'same' : 'changed', left, right });
		} else if (inB) {
			rows.push({ type: 'removed', left: line(k, b[k]), right: null });
		} else {
			rows.push({ type: 'added', left: null, right: line(k, a[k]) });
		}
	});
	rows.push({ type: 'context', left: '}', right: '}' });
	return rows;
};

const DiffCell = ({ row, side }) => {
	const value = side === 'left' ? row.left : row.right;
	let sign = ' ';
	let tone = 'text-neutral-400';
	if (value === null) {
		tone = 'bg-neutral-800/40';
	} else if (
		side === 'left' &&
		(row.type === 'removed' || row.type === 'changed')
	) {
		sign = '-';
		tone = 'bg-rose-500/15 text-rose-200';
	} else if (
		side === 'right' &&
		(row.type === 'added' || row.type === 'changed')
	) {
		sign = '+';
		tone = 'bg-emerald-500/15 text-emerald-200';
	}
	return (
		<div className={`min-w-full w-max whitespace-pre px-3 ${tone}`}>
			<span className="select-none opacity-40 mr-3">{sign}</span>
			{value ?? ' '}
		</div>
	);
};

/** Side-by-side before/after diff with synchronized horizontal scroll. */
const SplitDiff = ({ before, after }) => {
	const rows = buildDiffRows(before, after);
	const leftRef = useRef(null);
	const rightRef = useRef(null);
	const syncing = useRef(false);

	const sync = (src, dst) => {
		if (syncing.current || !src || !dst) return;
		syncing.current = true;
		dst.scrollLeft = src.scrollLeft;
		requestAnimationFrame(() => {
			syncing.current = false;
		});
	};

	return (
		<div className="grid grid-cols-2 gap-px rounded-md overflow-hidden bg-border font-mono text-xs leading-6">
			<div
				ref={leftRef}
				onScroll={() => sync(leftRef.current, rightRef.current)}
				className="overflow-x-auto bg-neutral-900"
			>
				{rows.map((row, i) => (
					<DiffCell key={i} row={row} side="left" />
				))}
			</div>
			<div
				ref={rightRef}
				onScroll={() => sync(rightRef.current, leftRef.current)}
				className="overflow-x-auto bg-neutral-900"
			>
				{rows.map((row, i) => (
					<DiffCell key={i} row={row} side="right" />
				))}
			</div>
		</div>
	);
};

/** Legacy string-list rendering (fabricated initial-setup entry). */
const FallbackList = ({ changes }) => (
	<div className="space-y-1.5">
		{changes.map((change, i) => (
			<div
				key={i}
				className="px-4 py-2.5 rounded-md bg-white border border-border font-mono text-sm text-foreground"
			>
				{change}
			</div>
		))}
	</div>
);

// ---------------------------------------------------------------------------
// Per-handler renderers, each: (task) => JSX | null
// ---------------------------------------------------------------------------

const tag = (name) => (v) =>
	(
		<>
			<span className="opacity-40">&lt;{name}&gt;</span>
			{v === '' || v == null ? EMPTY : v}
			<span className="opacity-40">&lt;/{name}&gt;</span>
		</>
	);

const metaDescription = (v) => (
	<>
		<span className="opacity-40">
			&lt;meta name=&quot;description&quot; content=&quot;
		</span>
		{v === '' || v == null ? EMPTY : v}
		<span className="opacity-40">&quot;&gt;</span>
	</>
);

/** Build a renderer for handlers that change one or more plain text fields. */
const fieldRenderer = (fields) => (task) => {
	const { before, after, context } = task;
	const blocks = fields
		.filter((f) => before[f.key] != null || after[f.key] != null)
		.map((f) => (
			<FieldDiff
				key={f.key}
				label={f.label}
				before={before[f.key] ?? ''}
				after={after[f.key] ?? ''}
				frame={f.frame}
				multiline={f.multiline}
			/>
		));
	if (!blocks.length) return null;
	return (
		<div className="space-y-3">
			{blocks}
			<ContextMeta context={context} />
		</div>
	);
};

const renderImgAlt = ({ before, after }) => {
	const prev = Object.fromEntries(
		(before.alts || []).map((x) => [x.src, x.alt])
	);
	const items = after.alts || [];
	if (!items.length) return null;
	return (
		<div className="space-y-2">
			{items.map((img, i) => (
				<div
					key={i}
					className="flex items-center gap-3 rounded-md border border-border bg-white p-2"
				>
					<img
						src={img.src}
						alt=""
						className="w-10 h-10 rounded object-cover bg-neutral-100 shrink-0"
					/>
					<div className="min-w-0 flex-1">
						<p className="small-medium text-foreground truncate m-0!">
							{fileName(img.src)}
						</p>
						<p className="font-mono text-xs truncate m-0!">
							<span className="text-rose-600">
								{prev[img.src] || EMPTY}
							</span>{' '}
							→{' '}
							<span className="text-emerald-600">
								{img.alt || EMPTY}
							</span>
						</p>
					</div>
				</div>
			))}
		</div>
	);
};

// Lines for one injected schema. Full payload when present; otherwise a
// representative stub with `[...]` standing in for the omitted properties.
const schemaLines = (entry) =>
	entry.injected_payload
		? JSON.stringify(entry.injected_payload, null, 2).split('\n')
		: ['{', `  "@type": "${entry.type}",`, '  [...]', '}'];

const renderSchema = ({ before, after, detail }) => {
	// Preferred: show the JSON-LD we injected, as a + diff.
	const injected = (detail?.per_type || []).filter(
		(e) => e.source === 'flavio'
	);
	if (injected.length) {
		return (
			<div className="space-y-2">
				{injected.map((entry, i) => (
					<AddedLines key={i} lines={schemaLines(entry)} />
				))}
			</div>
		);
	}

	// Fallback: just the @types present (added ones highlighted).
	const prev = new Set(before.schema_types || []);
	const types = after.schema_types || [];
	if (!types.length) return null;
	return (
		<div className="flex flex-wrap gap-2">
			{types.map((t) => {
				const isNew = !prev.has(t);
				return (
					<span
						key={t}
						className={`inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-xs font-medium ${
							isNew
								? 'bg-emerald-50 text-emerald-700'
								: 'bg-neutral-100 text-muted-foreground'
						}`}
					>
						{isNew && <span className="font-bold">+</span>}
						{t}
					</span>
				);
			})}
		</div>
	);
};

const renderDupeTitle = ({ before, after }) => {
	const prev = Object.fromEntries(
		(before.pages || []).map((p) => [p.url, p.title])
	);
	const pages = after.pages || [];
	if (!pages.length) return null;
	return (
		<div className="space-y-2">
			{pages.map((p, i) => (
				<div
					key={i}
					className="rounded-md border border-border bg-white p-2.5"
				>
					<p className="font-mono text-xs text-muted-foreground truncate mb-1 m-0!">
						{p.url}
					</p>
					<p className="font-mono text-xs truncate m-0!">
						<span className="text-rose-600">
							{prev[p.url] || EMPTY}
						</span>{' '}
						→ <span className="text-emerald-600">{p.title}</span>
					</p>
				</div>
			))}
		</div>
	);
};

const renderLocationPages = ({ after }) => {
	const pages = after.created_pages || [];
	if (!pages.length) return null;
	return (
		<div className="space-y-2">
			{pages.map((p, i) => (
				<div
					key={i}
					className="flex items-center gap-2.5 rounded-md border border-border bg-white p-2.5"
				>
					<MapPin className="w-4 h-4 text-muted-foreground shrink-0" />
					<div className="min-w-0">
						<p className="small-medium text-foreground truncate m-0!">
							{p.location_name || p.slug}
						</p>
						{p.slug && (
							<p className="font-mono text-xs text-muted-foreground truncate m-0!">
								/{p.slug}
							</p>
						)}
					</div>
				</div>
			))}
		</div>
	);
};

const renderContact = ({ before, after, context }) => {
	if (!before.has_contact_page && !after.has_contact_page) return null;
	const created = !before.has_contact_page && after.has_contact_page;
	return (
		<StatusCard
			title={created ? 'Contact page created' : 'Contact page in place'}
			hint={context?.page_path}
		/>
	);
};

const renderFaq = ({ after, context }) => {
	if (!after.faq_block_present) return null;
	return (
		<StatusCard
			title={`FAQ section added${context?.builder ? ` · ${context.builder}` : ''}`}
		/>
	);
};

const renderNap = ({ before, after, detail }) => {
	// Preferred: show the confirmed NAP values as a + diff.
	const values = detail?.expected_values;
	if (Array.isArray(values) && values.length) {
		return <AddedLines lines={values} />;
	}

	// Fallback: a status card with the NAP presence change.
	if (before.nap_present == null && after.nap_present == null) return null;
	const added = !before.nap_present && after.nap_present;
	return (
		<StatusCard
			title={
				added
					? 'NAP added to contact page'
					: 'NAP information verified'
			}
		/>
	);
};

const renderContentRewrite = ({ before, after }) => {
	const b = clip(before.content_raw);
	const a = clip(after.content_raw);
	if (!b && !a) return null;
	return <FieldDiff label="Content (excerpt)" before={b} after={a} multiline />;
};

// ---------------------------------------------------------------------------
// Registry
// ---------------------------------------------------------------------------

const RENDERERS = {
	optimizetitlesingle: fieldRenderer([
		{ key: 'title', label: 'Title', frame: tag('title') },
		{ key: 'site_name', label: 'Site name' },
	]),
	optimizedescsingle: fieldRenderer([
		{ key: 'description', label: 'Meta description', frame: metaDescription },
	]),
	tagline: fieldRenderer([{ key: 'tagline', label: 'Tagline' }]),
	friendlyurl: fieldRenderer([
		{ key: 'permalink_structure', label: 'Permalink structure' },
	]),
	robotstxtcontent: fieldRenderer([
		{ key: 'content', label: 'robots.txt', multiline: true },
	]),
	robotstxt: fieldRenderer([
		{ key: 'content', label: 'robots.txt', multiline: true },
	]),
	googbusinessoptimize: fieldRenderer([
		{ key: 'description', label: 'Google Business description', multiline: true },
	]),
	improvecontentsingle: renderContentRewrite,
	imgaltsingle: renderImgAlt,
	structureddatasingle: renderSchema,
	dupetitle: renderDupeTitle,
	locationpages: renderLocationPages,
	existcontact: renderContact,
	faqcontentsingle: renderFaq,
	checknap: renderNap,
};

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

const ChangeDetails = ({ task }) => {
	const renderer = RENDERERS[task.handler];
	let body = renderer ? renderer(task) : null;
	if (!body && (hasKeys(task.before) || hasKeys(task.after))) {
		body = <SplitDiff before={task.before} after={task.after} />;
	}
	if (!body && task.technicalChanges?.length > 0) {
		body = <FallbackList changes={task.technicalChanges} />;
	}
	if (!body) return null;

	return (
		<div>
			<p className="label-semibold uppercase tracking-wider text-green-600 mb-3">
				Technical changes
			</p>
			{body}
		</div>
	);
};

export default ChangeDetails;
