/**
 * Trial Setup Stream — live narration view for automated setup.
 *
 * Replaces the static "Analyzing your content..." spinner with an SSE-driven
 * stream of the strategy session's thinking, tool calls, and the final
 * confirmation gate.
 *
 * Part of FEAT-ChatDrivenSetup.
 */

import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import ReactDOM from 'react-dom';

const config = window.saiapSetupStream || {};
const { i18n = {} } = config;

/** Humanized label + icon for each tool name in the strategy session. */
const TOOL_LABELS = {
	list_posts: { label: 'Reading through your posts', icon: '📖' },
	get_post_content: { label: 'Studying a post in detail', icon: '📄' },
	get_keywords: { label: 'Sorting keyword candidates', icon: '🔑' },
	get_gsc_performance: { label: 'Pulling your Search Console data', icon: '📈' },
	submit_context_and_seeds: { label: 'Locking in topics and seed keywords', icon: '🌱' },
	submit_calendar: { label: 'Building your 30-day calendar', icon: '🗓️' },
	ask_user_question: { label: 'Checking in with you', icon: '🤝' },
};

function labelForTool(toolName) {
	return TOOL_LABELS[toolName] || { label: toolName, icon: '⚙️' };
}

/**
 * Fetch a short-lived stream token from the WP AJAX proxy.
 */
function fetchStreamToken() {
	const body = new FormData();
	body.append('action', 'saiap_setup_stream_token');
	body.append('nonce', config.nonce);

	return fetch(config.ajaxurl, { method: 'POST', body })
		.then((r) => r.json())
		.then((res) => {
			if (!res || !res.success || !res.data || !res.data.token) {
				throw new Error((res && res.data && res.data.message) || 'Token request failed');
			}
			return res.data;
		});
}

/**
 * Submit the user's confirmation-gate answer via the WP AJAX proxy.
 */
function submitAnswer(token, answer) {
	const body = new FormData();
	body.append('action', 'saiap_setup_stream_answer');
	body.append('nonce', config.nonce);
	body.append('token', token);
	body.append('answer', answer);
	return fetch(config.ajaxurl, { method: 'POST', body })
		.then((r) => r.json())
		.then((res) => {
			if (!res || !res.success) {
				throw new Error((res && res.data && res.data.message) || 'Answer submit failed');
			}
			return res.data;
		});
}

/**
 * Reducer-style normalized model of the narration feed.
 * - turns: [{ turn, iterations, complete }]
 * - items: flat list of rendered items for the history panel in order
 *   types: 'turn_marker' | 'tool' | 'question' | 'answer' | 'terminal' | 'error'
 *   each item carries `seq` (parsed from event id) so a `supersede` event can
 *   discard items whose seq is >= the supersede threshold.
 * - currentText: text currently streaming (ephemeral, not in items)
 * - pendingQuestion: current ask_user_question payload (or null), with `seq`
 *   so `supersede` can drop a stale gate from a failed attempt.
 */
function initialModel() {
	return {
		turns: [],
		items: [],
		currentText: '',
		currentTurn: null,
		currentIteration: null,
		pendingQuestion: null,
		terminal: null, // { kind: 'complete' | 'error', data }
		lastEventId: null,
	};
}

function parseSeq(eventId) {
	if (typeof eventId !== 'string') return 0;
	const n = parseInt(eventId.split(':')[1] || '0', 10);
	return Number.isFinite(n) ? n : 0;
}

function reduce(model, event) {
	const next = { ...model };
	if (event.id) next.lastEventId = event.id;
	const seq = parseSeq(event.id);

	switch (event.type) {
		case 'turn_start': {
			next.turns = [...model.turns, { turn: event.data.turn, complete: false, seq }];
			next.currentTurn = event.data.turn;
			next.currentIteration = 1;
			next.currentText = '';
			next.items = [
				...model.items,
				{ key: `turn-start-${event.id}`, seq, type: 'turn_marker', turn: event.data.turn },
			];
			return next;
		}

		case 'thinking_delta': {
			next.currentText = (model.currentText || '') + (event.data.delta || '');
			next.currentIteration = event.data.iteration || next.currentIteration;
			return next;
		}

		case 'tool_call': {
			// Commit any in-flight streamed text as an assistant bubble before
			// the tool card so the transcript reads chronologically.
			const committedItems = [...model.items];
			if (model.currentText && model.currentText.trim()) {
				committedItems.push({
					key: `text-${event.id}-pre`,
					seq,
					type: 'assistant_text',
					text: model.currentText,
				});
			}
			committedItems.push({
				key: `tool-${event.data.tool_use_id || event.id}`,
				seq,
				type: 'tool',
				toolName: event.data.tool_name,
				input: event.data.input,
				status: 'running',
			});
			next.items = committedItems;
			next.currentText = '';
			return next;
		}

		case 'tool_result': {
			next.items = model.items.map((item) => {
				if (
					item.type === 'tool' &&
					item.key === `tool-${event.data.tool_use_id}`
				) {
					return { ...item, status: 'complete', result: event.data.result };
				}
				return item;
			});
			return next;
		}

		case 'ask_user_question': {
			next.pendingQuestion = {
				seq,
				question: event.data.question,
				context: event.data.context,
				options: event.data.options,
				answered: false,
			};
			// Also drop a marker in the history so it's visible on scrollback.
			next.items = [
				...model.items,
				{
					key: `question-${event.id}`,
					seq,
					type: 'question',
					question: event.data.question,
					context: event.data.context,
				},
			];
			return next;
		}

		case 'question_answered': {
			next.pendingQuestion = next.pendingQuestion
				? { ...next.pendingQuestion, answered: true, answer: event.data.answer }
				: null;
			next.items = [
				...model.items,
				{ key: `answer-${event.id}`, seq, type: 'answer', answer: event.data.answer },
			];
			return next;
		}

		case 'supersede': {
			// A retry path on the backend pruned events with seq >= from_seq.
			// Drop matching items + clear pendingQuestion if it came from the
			// superseded range. Turns/state from earlier seqs are kept intact.
			const fromSeq = (event.data && event.data.from_seq) || 0;
			next.items = model.items.filter(
				(item) => typeof item.seq !== 'number' || item.seq < fromSeq
			);
			next.turns = model.turns.filter(
				(t) => typeof t.seq !== 'number' || t.seq < fromSeq
			);
			if (
				model.pendingQuestion &&
				typeof model.pendingQuestion.seq === 'number' &&
				model.pendingQuestion.seq >= fromSeq
			) {
				next.pendingQuestion = null;
			}
			next.currentText = '';
			next.currentIteration = null;
			return next;
		}

		case 'turn_complete': {
			next.turns = model.turns.map((t) =>
				t.turn === event.data.turn ? { ...t, complete: true } : t
			);
			return next;
		}

		case 'session_complete': {
			next.terminal = { kind: 'complete', data: event.data };
			return next;
		}

		case 'session_error': {
			next.terminal = { kind: 'error', data: event.data };
			next.items = [
				...model.items,
				{
					key: `error-${event.id}`,
					type: 'error',
					message: event.data.message || event.data.reason || 'Setup failed',
				},
			];
			return next;
		}

		default:
			return model;
	}
}

/**
 * SSE subscription hook. Opens EventSource with a query-param token,
 * feeds events into the reducer, auto-reconnects with Last-Event-ID.
 */
function useSetupStream({ apiBase, domain, token, onReady, onTerminal }) {
	const [model, setModel] = useState(initialModel);
	const [connState, setConnState] = useState('idle'); // idle | connecting | open | reconnecting | closed | failed
	const esRef = useRef(null);
	const retryRef = useRef(0);

	const applyEvent = useCallback((type, payload) => {
		setModel((prev) => reduce(prev, { type, id: payload.id, data: payload.data, ts: payload.ts }));
	}, []);

	useEffect(() => {
		if (!apiBase || !domain || !token) return undefined;

		let cancelled = false;

		function open(lastEventId) {
			setConnState(lastEventId ? 'reconnecting' : 'connecting');
			const url = new URL(apiBase.replace(/\/$/, '') + '/api/trial/setup/stream');
			url.searchParams.set('domain', domain);
			url.searchParams.set('token', token);
			if (lastEventId) url.searchParams.set('since', lastEventId);

			const es = new EventSource(url.toString());
			esRef.current = es;

			es.addEventListener('ready', () => {
				setConnState('open');
				retryRef.current = 0;
				if (onReady) onReady();
			});

			es.addEventListener('error', (evt) => {
				// Server sent an `event: error` or the transport broke.
				// EventSource will auto-reconnect unless closed.
				if (es.readyState === EventSource.CLOSED) {
					if (cancelled) return;
					retryRef.current += 1;
					if (retryRef.current > 6) {
						setConnState('failed');
						return;
					}
					// Manual reconnect using last-known event id.
					setModel((prev) => {
						setTimeout(() => open(prev.lastEventId), 1500);
						return prev;
					});
					setConnState('reconnecting');
				}
			});

			// Register listeners for each event type we care about.
			const types = [
				'turn_start',
				'thinking_delta',
				'tool_call',
				'tool_result',
				'turn_complete',
				'ask_user_question',
				'question_answered',
				'session_complete',
				'session_error',
				'supersede',
			];
			types.forEach((t) => {
				es.addEventListener(t, (evt) => {
					try {
						const payload = JSON.parse(evt.data || '{}');
						const id = evt.lastEventId || payload.id;
						applyEvent(t, { id, data: payload.data, ts: payload.ts });
						if (t === 'session_complete' || t === 'session_error') {
							setConnState('closed');
							if (onTerminal) onTerminal(t, payload.data);
							es.close();
						}
					} catch (err) {
						console.error('[setup-stream] failed to parse event', t, err);
					}
				});
			});

			es.addEventListener('terminal', () => {
				setConnState('closed');
				es.close();
			});
		}

		open(null);

		return () => {
			cancelled = true;
			if (esRef.current) {
				esRef.current.close();
				esRef.current = null;
			}
		};
	}, [apiBase, domain, token, applyEvent, onReady, onTerminal]);

	return { model, connState };
}

// ---------------------------------------------------------------------------
// Components
// ---------------------------------------------------------------------------

function TurnMarker({ turn }) {
	const title =
		turn === 1
			? 'Step 1 · Getting to know your site'
			: turn === 2
				? 'Step 2 · Planning your content'
				: `Step ${turn}`;
	return (
		<div className="saiap-setup-stream__turn-marker" aria-label={title}>
			<span className="saiap-setup-stream__turn-number">{turn}</span>
			<span className="saiap-setup-stream__turn-label">{title}</span>
		</div>
	);
}

function AssistantText({ text }) {
	return <div className="saiap-setup-stream__text">{text}</div>;
}

function ToolCard({ toolName, input, status, result }) {
	const [expanded, setExpanded] = useState(false);
	const { label, icon } = labelForTool(toolName);
	return (
		<div
			className={`saiap-setup-stream__tool saiap-setup-stream__tool--${status}`}
			onClick={() => setExpanded((v) => !v)}
			role="button"
			tabIndex={0}
		>
			<span className="saiap-setup-stream__tool-icon" aria-hidden="true">
				{icon}
			</span>
			<span className="saiap-setup-stream__tool-label">{label}</span>
			<span className="saiap-setup-stream__tool-status" aria-label={status}>
				{status === 'running' ? (
					<span className="saiap-setup-stream__spinner" />
				) : (
					<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
						<polyline points="20 6 9 17 4 12" />
					</svg>
				)}
			</span>
			{expanded && (
				<div className="saiap-setup-stream__tool-details">
					<pre>{JSON.stringify({ input, result }, null, 2)}</pre>
				</div>
			)}
		</div>
	);
}

function QuestionMarker({ question, context }) {
	return (
		<div className="saiap-setup-stream__question-marker">
			<div className="saiap-setup-stream__question-text">{question}</div>
			{context && <div className="saiap-setup-stream__question-context">{context}</div>}
		</div>
	);
}

function AnswerMarker({ answer }) {
	return (
		<div className="saiap-setup-stream__answer-marker">
			<span className="saiap-setup-stream__answer-label">You:</span>
			<span className="saiap-setup-stream__answer-text">{answer}</span>
		</div>
	);
}

function ErrorBanner({ message }) {
	return (
		<div className="saiap-setup-stream__error-banner">
			<strong>{i18n.failed || 'Setup hit an error.'}</strong>
			<span>{message}</span>
		</div>
	);
}

function ConfirmationCard({ pendingQuestion, token, onAnswered }) {
	const [busy, setBusy] = useState(false);
	const [freeText, setFreeText] = useState('');
	const [error, setError] = useState('');

	const submit = async (answer) => {
		if (busy || !answer) return;
		setBusy(true);
		setError('');
		try {
			await submitAnswer(token, answer);
			onAnswered(answer);
		} catch (err) {
			setError(err.message || i18n.answerFailed);
			setBusy(false);
		}
	};

	if (pendingQuestion.answered) {
		return (
			<div className="saiap-setup-stream__confirmation saiap-setup-stream__confirmation--answered">
				<div className="saiap-setup-stream__confirmation-q">
					{pendingQuestion.question}
				</div>
				<div className="saiap-setup-stream__confirmation-a">
					{i18n.answerSent || "Thanks — generating your first post now..."}
				</div>
			</div>
		);
	}

	return (
		<div className="saiap-setup-stream__confirmation">
			<div className="saiap-setup-stream__confirmation-q">{pendingQuestion.question}</div>
			{pendingQuestion.context && (
				<div className="saiap-setup-stream__confirmation-ctx">{pendingQuestion.context}</div>
			)}
			{Array.isArray(pendingQuestion.options) && pendingQuestion.options.length > 0 && (
				<div className="saiap-setup-stream__confirmation-options">
					{pendingQuestion.options.map((opt) => (
						<button
							key={opt}
							type="button"
							className="saiap-setup-stream__confirmation-option"
							disabled={busy}
							onClick={() => submit(opt)}
						>
							{opt}
						</button>
					))}
				</div>
			)}
			<div className="saiap-setup-stream__confirmation-freetext">
				<textarea
					placeholder="Or tell me what you'd like to change..."
					value={freeText}
					onChange={(e) => setFreeText(e.target.value)}
					disabled={busy}
					rows={2}
				/>
				<button
					type="button"
					className="saiap-setup-stream__confirmation-send"
					disabled={busy || !freeText.trim()}
					onClick={() => submit(freeText.trim())}
				>
					Send
				</button>
			</div>
			{error && <div className="saiap-setup-stream__confirmation-error">{error}</div>}
		</div>
	);
}

function ConnectionStatus({ state }) {
	if (state === 'open') return null;
	let text = '';
	if (state === 'idle' || state === 'connecting') text = i18n.connecting || 'Connecting...';
	else if (state === 'reconnecting') text = i18n.disconnected || 'Reconnecting...';
	else if (state === 'failed') text = i18n.failed || 'Connection failed.';
	else if (state === 'closed') return null;
	if (!text) return null;
	return <div className="saiap-setup-stream__conn">{text}</div>;
}

// ---------------------------------------------------------------------------
// Top-level
// ---------------------------------------------------------------------------

function SetupStream() {
	const [token, setToken] = useState('');
	const [apiBase, setApiBase] = useState(config.apiBase || '');
	const [domain, setDomain] = useState(config.domain || '');
	const [initError, setInitError] = useState('');
	const [answeredOnce, setAnsweredOnce] = useState(false);

	useEffect(() => {
		let cancelled = false;
		fetchStreamToken()
			.then((res) => {
				if (cancelled) return;
				setToken(res.token);
				if (res.apiBase) setApiBase(res.apiBase);
				if (res.domain) setDomain(res.domain);
			})
			.catch((err) => {
				if (!cancelled) setInitError(err.message || 'Failed to connect');
			});
		return () => {
			cancelled = true;
		};
	}, []);

	const handleTerminal = useCallback((type) => {
		// Once the session completes, reload the dashboard so the PHP-rendered
		// calendar view replaces this stream UI with the populated calendar.
		if (type === 'session_complete') {
			setTimeout(() => {
				window.location.reload();
			}, 1800);
		}
	}, []);

	const { model, connState } = useSetupStream({
		apiBase,
		domain,
		token,
		onTerminal: handleTerminal,
	});

	const handleAnswered = useCallback((answer) => {
		setAnsweredOnce(true);
	}, []);

	// Auto-scroll the history panel to bottom when new content arrives, unless
	// the user has manually scrolled up.
	const historyRef = useRef(null);
	const pinnedRef = useRef(true);
	useEffect(() => {
		const el = historyRef.current;
		if (!el || !pinnedRef.current) return;
		el.scrollTop = el.scrollHeight;
	}, [model.items, model.currentText]);

	const onHistoryScroll = useCallback(() => {
		const el = historyRef.current;
		if (!el) return;
		const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 40;
		pinnedRef.current = atBottom;
	}, []);

	const currentStatusLabel = useMemo(() => {
		if (model.terminal?.kind === 'complete') {
			return i18n.done || 'Setup complete — loading your dashboard...';
		}
		if (model.terminal?.kind === 'error') {
			return i18n.failed || 'Setup hit an error.';
		}
		if (!token) return i18n.preparing || 'Warming up...';
		return null;
	}, [model.terminal, token]);

	if (initError && !token) {
		return (
			<div className="saiap-setup-stream">
				<ErrorBanner message={initError} />
			</div>
		);
	}

	return (
		<div className="saiap-setup-stream">
			<div className="saiap-setup-stream__header">
				<div className="saiap-setup-stream__header-title">
					Clarence is building your content plan
				</div>
				<div className="saiap-setup-stream__header-sub">
					This takes a few minutes. Follow along below — I&apos;ll check in with you before writing anything.
				</div>
			</div>

			<div
				className="saiap-setup-stream__history"
				ref={historyRef}
				onScroll={onHistoryScroll}
			>
				{model.items.length === 0 && !model.currentText && (
					<div className="saiap-setup-stream__empty">
						{i18n.preparing || 'Warming up the strategy session...'}
					</div>
				)}

				{model.items.map((item) => {
					switch (item.type) {
						case 'turn_marker':
							return <TurnMarker key={item.key} turn={item.turn} />;
						case 'assistant_text':
							return <AssistantText key={item.key} text={item.text} />;
						case 'tool':
							return (
								<ToolCard
									key={item.key}
									toolName={item.toolName}
									input={item.input}
									status={item.status}
									result={item.result}
								/>
							);
						case 'question':
							return (
								<QuestionMarker
									key={item.key}
									question={item.question}
									context={item.context}
								/>
							);
						case 'answer':
							return <AnswerMarker key={item.key} answer={item.answer} />;
						case 'error':
							return <ErrorBanner key={item.key} message={item.message} />;
						default:
							return null;
					}
				})}
			</div>

			<div className="saiap-setup-stream__current">
				{model.currentText ? (
					<div className="saiap-setup-stream__current-text">{model.currentText}</div>
				) : currentStatusLabel ? (
					<div className="saiap-setup-stream__current-status">{currentStatusLabel}</div>
				) : connState !== 'open' ? (
					<ConnectionStatus state={connState} />
				) : null}
			</div>

			{model.pendingQuestion && !answeredOnce && (
				<ConfirmationCard
					pendingQuestion={model.pendingQuestion}
					token={token}
					onAnswered={handleAnswered}
				/>
			)}

			{model.pendingQuestion && model.pendingQuestion.answered && (
				<div className="saiap-setup-stream__confirmation-done">
					{i18n.answerSent || "Thanks — generating your first post now..."}
				</div>
			)}
		</div>
	);
}

document.addEventListener('DOMContentLoaded', () => {
	const root = document.getElementById('saiap-setup-stream-root');
	if (root) {
		ReactDOM.render(<SetupStream />, root);
	}
});
