import { useMemo, useState } from 'react';
import {
	ArrowLeft,
	Check,
	X,
	Loader2,
	Sparkles,
	Copy,
	FileText,
	RotateCcw,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import FlavioIcon from '@/components/ui/flavio-icon';
import { isAppPaused } from '@/api/client';
import { useInterventionResponse } from '@/features/interventions/useInterventionResponse';
import UpgradeLock from '@/features/interventions/detail/UpgradeLock';

/**
 * Detail for the `robotstxtcontent` / `rewrite` intervention (type selection).
 *
 * Flavio audits the site's robots.txt against three baseline rules and, when
 * any fails, drafts a canonical version that fixes them. The user picks the
 * new file or keeps the current one; nothing is editable here, only chosen.
 *
 * Contract metadata: `violations:string[]`, `rules:{A_public_pages_allowed,
 * B_private_disallowed, C_sitemap_referenced}` (each "pass" | "fail"),
 * `sitemap_referenced:string|null`, and `options:[{id:"apply_canonical", value},
 * {id:"keep_current", value}]` where each `value` is the actual robots.txt body.
 * userResponse: { choice:"apply_canonical" | "keep_current" }.
 *
 * Both options are real decisions the handler records, so both submit as
 * `acknowledge` (neither is a dismiss; no confirmation dialog).
 *
 * Defensive on metadata: every field is read with a fallback. A missing rule is
 * treated as `fail` so we don't claim a check passed when we don't know.
 */
const RULES = [
	{
		key: 'A_public_pages_allowed',
		label: 'Public pages are crawlable',
		passHelp: 'Google can read the pages you want to show in search.',
		failHelp:
			"Blocked. Google can't see one or more pages you want to show in search.",
	},
	{
		key: 'B_private_disallowed',
		label: 'Admin paths are blocked',
		passHelp:
			'Login, dashboard and similar URLs stay out of search results.',
		failHelp:
			"Exposed. Admin URLs could end up in Google's search results.",
	},
	{
		key: 'C_sitemap_referenced',
		label: 'Sitemap is referenced',
		passHelp: 'robots.txt points Google at your sitemap.',
		failHelp: "Missing. Google can't find your sitemap automatically.",
	},
];

/** One rule card, pass or fail, in the top grid. */
const RuleCard = ({ label, pass, help }) => (
	<div className="rounded-2xl border border-border p-4 flex items-start gap-3">
		{pass ? (
			<span className="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-emerald-50 text-emerald-600 shrink-0">
				<Check className="w-4 h-4" />
			</span>
		) : (
			<span className="inline-flex items-center justify-center w-7 h-7 rounded-lg bg-red-50 text-red-600 shrink-0">
				<X className="w-4 h-4" />
			</span>
		)}
		<div className="min-w-0">
			<p
				className={`small-semibold my-0! ${
					pass ? 'text-foreground' : 'text-red-700'
				}`}
			>
				{label}
			</p>
			<p className="small-regular text-muted-foreground my-0! mt-1!">
				{help}
			</p>
		</div>
	</div>
);

/** Line-by-line LCS diff. Returns rows tagged as added/removed/context. */
const computeDiff = (currentText, canonicalText) => {
	const a = (currentText || '').split('\n');
	const b = (canonicalText || '').split('\n');
	const m = a.length;
	const n = b.length;
	// dp[i][j] = LCS length of a[i..] vs b[j..]
	const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
	for (let i = m - 1; i >= 0; i--) {
		for (let j = n - 1; j >= 0; j--) {
			if (a[i] === b[j]) dp[i][j] = dp[i + 1][j + 1] + 1;
			else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j + 1]);
		}
	}
	const out = [];
	let i = 0;
	let j = 0;
	while (i < m && j < n) {
		if (a[i] === b[j]) {
			out.push({ type: 'context', line: a[i] });
			i++;
			j++;
		} else if (dp[i + 1][j] >= dp[i][j + 1]) {
			out.push({ type: 'removed', line: a[i] });
			i++;
		} else {
			out.push({ type: 'added', line: b[j] });
			j++;
		}
	}
	while (i < m) out.push({ type: 'removed', line: a[i++] });
	while (j < n) out.push({ type: 'added', line: b[j++] });
	return out;
};

/** Rendered diff or full-file view; both share gutters so the eye stays aligned. */
const FileLines = ({ rows }) => {
	let lineNumber = 0;
	return (
		<div className="font-mono text-sm">
			{rows.length === 0 ? (
				<div className="px-4 py-6 small-regular italic text-muted-foreground/70 text-center">
					Empty, no robots.txt yet.
				</div>
			) : (
				rows.map((row, idx) => {
					const isRemoved = row.type === 'removed';
					if (!isRemoved) lineNumber++;
					const bg =
						row.type === 'added'
							? 'bg-emerald-50/70'
							: row.type === 'removed'
								? 'bg-red-50/70'
								: '';
					const edge =
						row.type === 'added'
							? 'border-l-2 border-emerald-500'
							: row.type === 'removed'
								? 'border-l-2 border-red-400'
								: 'border-l-2 border-transparent';
					const tone =
						row.type === 'added'
							? 'text-emerald-700'
							: row.type === 'removed'
								? 'text-red-700'
								: 'text-foreground';
					const marker =
						row.type === 'added'
							? '+'
							: row.type === 'removed'
								? '-'
								: ' ';
					return (
						<div
							key={idx}
							className={`flex items-stretch ${edge} ${bg}`}
						>
							<span className="w-10 text-right pr-2 py-0.5 text-muted-foreground/50 select-none shrink-0">
								{isRemoved ? '' : lineNumber}
							</span>
							<span
								className={`w-5 text-center py-0.5 select-none shrink-0 ${tone}`}
							>
								{marker}
							</span>
							<span
								className={`flex-1 min-w-0 py-0.5 pr-3 whitespace-pre-wrap break-words ${tone}`}
							>
								{row.line || ' '}
							</span>
						</div>
					);
				})
			)}
		</div>
	);
};

/** A pair of tab pills used in the file block header. */
const TabSwitch = ({ value, options, onChange }) => (
	<span className="inline-flex items-center gap-1 rounded-lg bg-muted p-1">
		{options.map((o) => (
			<button
				key={o.value}
				type="button"
				onClick={() => onChange(o.value)}
				className={`px-3 py-1 rounded-md small-medium transition-colors cursor-pointer ${
					value === o.value
						? 'bg-background text-foreground shadow-sm'
						: 'text-muted-foreground hover:text-foreground'
				}`}
			>
				{o.label}
			</button>
		))}
	</span>
);

/** Render a violation string, with `backticked` bits styled as inline code. */
const renderViolation = (text) => {
	const parts = String(text).split('`');
	return parts.map((p, i) =>
		i % 2 === 1 ? (
			<code
				key={i}
				className="font-mono text-sm bg-muted/60 text-foreground px-1.5 py-0.5 rounded"
			>
				{p}
			</code>
		) : (
			<span key={i}>{p}</span>
		)
	);
};

const RobotsTxt = ({
	intervention = {},
	interventionId,
	onBack,
	onResolved,
}) => {
	const m = intervention.metadata || {};
	const rules = m.rules || {};
	const violations = Array.isArray(m.violations) ? m.violations : [];
	const options = Array.isArray(m.options) ? m.options : [];
	const findOption = (id) => options.find((o) => o?.id === id);
	const canonical = findOption('apply_canonical')?.value || '';
	const current = findOption('keep_current')?.value || '';
	// Trial ended: the intervention can't be acted on, so its action buttons are
	// swapped for the single "unlock" upgrade CTA.
	const isPaused = isAppPaused();

	const [view, setView] = useState('diff');
	const [copied, setCopied] = useState(false);

	const diff = useMemo(
		() => computeDiff(current, canonical),
		[current, canonical]
	);
	const addedCount = diff.filter((r) => r.type === 'added').length;
	const removedCount = diff.filter((r) => r.type === 'removed').length;
	const noChanges = addedCount === 0 && removedCount === 0;

	const currentRows = useMemo(
		() =>
			(current || '').split('\n').map((line) => ({ type: 'context', line })),
		[current]
	);

	const { pending, resolved, error, run } = useInterventionResponse(
		interventionId,
		{ onResolved }
	);

	const copy = async () => {
		const text = view === 'diff' ? canonical : current;
		try {
			await navigator.clipboard.writeText(text);
			setCopied(true);
			setTimeout(() => setCopied(false), 1500);
		} catch {
			/* clipboard blocked; silent */
		}
	};

	if (resolved) {
		return (
			<div className="max-w-2xl mx-auto text-center">
				<FlavioIcon className="w-12 h-12 mx-auto mb-4" />
				<h1 className="heading-h2 mt-0! leading-tight mb-3">
					{resolved === 'primary' ? 'All set' : 'Got it'}
				</h1>
				<div className="max-w-md mx-auto">
					<p className="paragraph-regular text-muted-foreground mb-0!">
						{resolved === 'primary'
							? "Great. I'll update your robots.txt shortly."
							: "No problem. I'll leave your robots.txt as it is."}
					</p>
				</div>
				{onBack && (
					<Button
						onClick={onBack}
						size="lg"
						className="mt-6 bg-foreground text-background! hover:bg-foreground/90"
					>
						<ArrowLeft />
						Back to your list
					</Button>
				)}
			</div>
		);
	}

	const failCount = RULES.reduce(
		(n, r) => n + (rules[r.key] === 'pass' ? 0 : 1),
		0
	);

	return (
		<div className="max-w-3xl mx-auto">
			<div className="text-center">
				<h1 className="heading-h1 mt-0! leading-tight mb-3">
					Update your robots.txt
				</h1>
				<div className="max-w-xl mx-auto mb-8">
					<p className="paragraph-regular text-muted-foreground mb-0!">
						This file tells Google what to crawl on your site.{' '}
						{failCount === 0
							? "Nothing critical is broken, but here's what could be tighter."
							: `I found ${failCount} thing${failCount === 1 ? '' : 's'} to fix and drafted the change below.`}
					</p>
				</div>
			</div>

			{/* Audit summary */}
			<p className="small-semibold uppercase tracking-wide text-muted-foreground mb-3 mt-0!">
				What I checked
			</p>
			<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-6">
				{RULES.map((r) => {
					const pass = rules[r.key] === 'pass';
					return (
						<RuleCard
							key={r.key}
							label={r.label}
							pass={pass}
							help={pass ? r.passHelp : r.failHelp}
						/>
					);
				})}
			</div>

			{/* File block: header (path + delta + tabs + copy) over the body */}
			<div className="rounded-2xl border border-border overflow-hidden">
				<div className="flex items-center gap-3 px-4 py-2.5 bg-muted/30 border-b border-border">
					<FileText className="w-4 h-4 text-muted-foreground shrink-0" />
					<span className="font-mono text-sm text-foreground">
						/robots.txt
					</span>
					{!noChanges && (
						<span className="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 text-emerald-700 px-2 py-0.5 small-medium">
							{addedCount > 0 &&
								`+${addedCount} line${addedCount === 1 ? '' : 's'}`}
							{addedCount > 0 && removedCount > 0 && ' · '}
							{removedCount > 0 &&
								`-${removedCount} line${removedCount === 1 ? '' : 's'}`}
						</span>
					)}
					<span className="ml-auto flex items-center gap-2">
						<TabSwitch
							value={view}
							onChange={setView}
							options={[
								{ value: 'diff', label: "What's changing" },
								{ value: 'current', label: 'Current' },
							]}
						/>
						<button
							type="button"
							onClick={copy}
							aria-label="Copy"
							title="Copy"
							className="inline-flex items-center justify-center w-8 h-8 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors cursor-pointer"
						>
							{copied ? (
								<Check className="w-4 h-4 text-emerald-600" />
							) : (
								<Copy className="w-4 h-4" />
							)}
						</button>
					</span>
				</div>
				<div className="bg-background">
					{view === 'diff' ? (
						<FileLines rows={diff} />
					) : (
						<FileLines rows={currentRows} />
					)}
				</div>
			</div>

			{/* Violation explanations under the file (one line per finding) */}
			{violations.length > 0 && (
				<div className="mt-4 space-y-2">
					{violations.map((v, i) => (
						<div
							key={i}
							className="flex items-start gap-2 small-regular text-muted-foreground"
						>
							<Sparkles className="w-4 h-4 shrink-0 mt-0.5 text-magenta-500" />
							<span>{renderViolation(v)}</span>
						</div>
					))}
				</div>
			)}

			<div className="flex items-center justify-center gap-3 mt-8">
				{isPaused ? (
					<UpgradeLock />
				) : (
					<>
						<Button
							onClick={() =>
								run('primary', {
									status: 'acknowledge',
									userResponse: { choice: 'apply_canonical' },
								})
							}
							disabled={!!pending || !!resolved}
							size="lg"
							className="bg-foreground text-background! hover:bg-foreground/90"
						>
							{pending === 'primary' ? (
								<Loader2 className="animate-spin" />
							) : (
								<Check />
							)}
							Use the new robots.txt
						</Button>
						<Button
							onClick={() =>
								run('secondary', {
									status: 'acknowledge',
									userResponse: { choice: 'keep_current' },
								})
							}
							disabled={!!pending || !!resolved}
							size="lg"
							variant="outline"
						>
							{pending === 'secondary' ? (
								<Loader2 className="animate-spin" />
							) : (
								<RotateCcw />
							)}
							Keep current
						</Button>
					</>
				)}
			</div>

			{!isPaused && error && (
				<p className="small-regular text-destructive text-center mt-3 mb-0!">
					{error}
				</p>
			)}
			{!isPaused && (
				<p className="small-regular text-muted-foreground text-center mt-4 mb-0!">
					Nothing changes until you choose.
				</p>
			)}
		</div>
	);
};

export default RobotsTxt;
