import {__, _n, sprintf} from '@wordpress/i18n';
import {useEffect, useMemo, useRef, useState} from '@wordpress/element';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import Alert from 'react-bootstrap/Alert';
import Spinner from 'react-bootstrap/Spinner';
import InputGroup from 'react-bootstrap/InputGroup';

/**
 * Chapters tab — edit, add, generate, and save the video's chapter list.
 *
 * Layout:
 *   - Header + video-length note (max end time for chapters)
 *   - One editable row per chapter: index | start | to | end | title | X
 *   - + Add new Chapter
 *   - Generate Smart Chapters (disabled when the video has no captions;
 *     see the server-side gate in ajax_generate_chapters for why)
 *   - Save Chapters (enabled only when the edit set differs from server)
 *
 * Chapter times are stored as seconds. Inputs are text and accept
 * mm:ss or h:mm:ss; on blur, parse to seconds. If a value can't be
 * parsed, the input clamps back to the prior valid value.
 */

const POLL_INTERVAL_MS = 6000;
const POLL_MAX_MS = 3 * 60 * 1000;

export default function VideoChapters({video, setVideos}) {
	const [chapters, setChapters] = useState(() => normalize(video.chapters || []));
	const [saving, setSaving] = useState(false);
	const [generating, setGenerating] = useState(false);
	const [polling, setPolling] = useState(false);
	const [message, setMessage] = useState(null);
	const pollTimer = useRef(null);
	const pollStarted = useRef(null);

	const hasCaptions = (video.captions || []).length > 0;
	const captionCount = (video.captions || []).length;
	const videoLength = Math.max(0, Number(video.length) || 0);
	const serverChapters = useMemo(
		() => JSON.stringify(normalize(video.chapters || [])),
		[video.guid, video.chapters]
	);
	const dirty = JSON.stringify(chapters) !== serverChapters;

	useEffect(() => {
		setChapters(normalize(video.chapters || []));
	}, [video.guid, video.chapters && video.chapters.length]);

	useEffect(() => {
		return () => {
			if (pollTimer.current) {
				clearTimeout(pollTimer.current);
				pollTimer.current = null;
			}
		};
	}, []);

	// ---------------------------------------------------------------------
	// Edit handlers
	// ---------------------------------------------------------------------

	function updateChapter(index, patch) {
		setChapters((prev) =>
			prev.map((c, i) => (i === index ? {...c, ...patch} : c))
		);
	}

	function addChapter() {
		const lastEnd = chapters.length
			? chapters[chapters.length - 1].end
			: 0;
		setChapters([
			...chapters,
			{start: lastEnd, end: videoLength, title: ''},
		]);
	}

	function removeChapter(index) {
		setChapters(chapters.filter((_, i) => i !== index));
	}

	// ---------------------------------------------------------------------
	// Save
	// ---------------------------------------------------------------------

	function patchParentChapters(next) {
		if (typeof setVideos === 'function') {
			setVideos((videos) =>
				videos.map((v) =>
					v.guid === video.guid ? {...v, chapters: next} : v
				)
			);
		}
	}

	function validate() {
		let prevEnd = -1;
		for (let i = 0; i < chapters.length; i++) {
			const c = chapters[i];
			if (!(c.end > c.start)) {
				return sprintf(
					/* translators: %d: chapter number, 1-indexed */
					__('Chapter %d has an invalid time range.', 'infinite-uploads'),
					i + 1
				);
			}
			if (videoLength > 0 && c.end > videoLength) {
				return sprintf(
					__('Chapter %d ends past the video length.', 'infinite-uploads'),
					i + 1
				);
			}
			if (c.start < prevEnd) {
				return sprintf(
					__('Chapter %d overlaps the previous chapter.', 'infinite-uploads'),
					i + 1
				);
			}
			prevEnd = c.end;
		}
		return null;
	}

	function onSave() {
		const err = validate();
		if (err) {
			setMessage({variant: 'warning', text: err});
			return;
		}
		setMessage(null);
		setSaving(true);
		const fd = new FormData();
		fd.append('action', 'infinite-uploads-video-save-chapters');
		fd.append('nonce', IUP_VIDEO.nonce);
		fd.append('video_id', video.guid);
		fd.append('chapters', JSON.stringify(chapters));

		fetch(ajaxurl, {
			method: 'POST',
			headers: {Accept: 'application/json'},
			body: fd,
		})
			.then((r) => r.json())
			.then((data) => {
				if (data && data.success) {
					const next = normalize(data.data.chapters || []);
					setChapters(next);
					patchParentChapters(next);
					setMessage({
						variant: 'success',
						text: __('Chapters saved.', 'infinite-uploads'),
					});
				} else {
					setMessage({
						variant: 'danger',
						text: extractError(data) || __(
							'Could not save chapters.',
							'infinite-uploads'
						),
					});
				}
			})
			.catch(() => {
				setMessage({
					variant: 'danger',
					text: __('Could not save chapters.', 'infinite-uploads'),
				});
			})
			.finally(() => setSaving(false));
	}

	// ---------------------------------------------------------------------
	// Smart Chapters — trigger + poll
	// ---------------------------------------------------------------------

	function pollOnce() {
		fetch(
			`${ajaxurl}?action=infinite-uploads-video-fetch&video_id=${encodeURIComponent(
				video.guid
			)}&nonce=${IUP_VIDEO.nonce}`,
			{method: 'GET', headers: {Accept: 'application/json'}}
		)
			.then((r) => r.json())
			.then((data) => {
				if (data && data.success && data.data) {
					const fresh = normalize(data.data.chapters || []);
					if (fresh.length) {
						setChapters(fresh);
						patchParentChapters(fresh);
						setPolling(false);
						pollTimer.current = null;
						setMessage({
							variant: 'success',
							text: __(
								'Smart Chapters ready',
								'infinite-uploads'
							),
						});
						return;
					}
				}
				const elapsed = Date.now() - (pollStarted.current || Date.now());
				if (elapsed >= POLL_MAX_MS) {
					setPolling(false);
					pollTimer.current = null;
					setMessage({
						variant: 'info',
						text: __(
							'Still generating. Close and reopen the video in a minute to see the chapters.',
							'infinite-uploads'
						),
					});
					return;
				}
				pollTimer.current = setTimeout(pollOnce, POLL_INTERVAL_MS);
			})
			.catch(() => {
				setPolling(false);
				pollTimer.current = null;
				setMessage({
					variant: 'danger',
					text: __(
						'Could not check for chapters. Try refreshing in a moment.',
						'infinite-uploads'
					),
				});
			});
	}

	function onGenerate() {
		setMessage(null);
		setGenerating(true);
		const fd = new FormData();
		fd.append('action', 'infinite-uploads-video-generate-chapters');
		fd.append('nonce', IUP_VIDEO.nonce);
		fd.append('video_id', video.guid);

		fetch(ajaxurl, {
			method: 'POST',
			headers: {Accept: 'application/json'},
			body: fd,
		})
			.then((r) => r.json())
			.then((data) => {
				if (data && data.success) {
					setGenerating(false);
					setPolling(true);
					pollStarted.current = Date.now();
					pollTimer.current = setTimeout(pollOnce, POLL_INTERVAL_MS);
				} else {
					setGenerating(false);
					setMessage({
						variant: 'danger',
						text: extractError(data) || __(
							'Could not start chapter generation.',
							'infinite-uploads'
						),
					});
				}
			})
			.catch(() => {
				setGenerating(false);
				setMessage({
					variant: 'danger',
					text: __(
						'Could not start chapter generation.',
						'infinite-uploads'
					),
				});
			});
	}

	// ---------------------------------------------------------------------
	// Render
	// ---------------------------------------------------------------------

	return (
		<div className="iup-video-chapters">
			<h5 className="mb-2">{__('Chapters', 'infinite-uploads')}</h5>
			<p className="text-muted mb-4">
				{__(
					'Chapters are displayed in the timeline and let viewers navigate between sections of the video.',
					'infinite-uploads'
				)}{' '}
				{__('Chapters must stay inside the total video length:', 'infinite-uploads')}{' '}
				<code className="text-danger">{formatTime(videoLength)}</code>
			</p>

			{message && (
				<Alert
					variant={message.variant}
					className="mb-3"
					dismissible
					onClose={() => setMessage(null)}
				>
					{message.text}
				</Alert>
			)}

			<div className="iup-chapter-rows mb-3">
				{chapters.length === 0 && (
					<p className="text-muted small">
						{__(
							'No chapters on this video yet. Add one below, or generate Smart Chapters from your captions.',
							'infinite-uploads'
						)}
					</p>
				)}
				{chapters.map((chapter, i) => (
					<ChapterRow
						key={i}
						index={i + 1}
						chapter={chapter}
						videoLength={videoLength}
						disabled={saving || polling}
						onChange={(patch) => updateChapter(i, patch)}
						onRemove={() => removeChapter(i)}
					/>
				))}
			</div>

			<div className="d-flex flex-wrap gap-2 mb-3">
				<Button
					variant="outline-secondary"
					onClick={addChapter}
					disabled={saving || polling}
				>
					<span className="dashicons dashicons-plus-alt2 me-1"></span>
					{__('Add new Chapter', 'infinite-uploads')}
				</Button>
				<Button
					variant="outline-primary"
					onClick={onGenerate}
					disabled={!hasCaptions || generating || polling || saving}
					title={
						!hasCaptions
							? __(
									'Upload a caption track first — Smart Chapters are generated from the caption transcript.',
									'infinite-uploads'
								)
							: undefined
					}
				>
					{generating ? (
						<>
							<Spinner
								animation="border"
								size="sm"
								className="me-2"
							/>
							{__('Queueing…', 'infinite-uploads')}
						</>
					) : polling ? (
						<>
							<Spinner
								animation="border"
								size="sm"
								className="me-2"
							/>
							{__('Generating…', 'infinite-uploads')}
						</>
					) : (
						<>
							<span className="dashicons dashicons-superhero-alt me-1"></span>
							{__('Generate Smart Chapters', 'infinite-uploads')}
						</>
					)}
				</Button>
			</div>

			{!hasCaptions && (
				<p className="text-muted small mb-3">
					{__(
						'Smart Chapters require an uploaded caption track. Add a WebVTT or SRT file on the Captions tab, then come back here.',
						'infinite-uploads'
					)}
				</p>
			)}

			{hasCaptions && polling && (
				<p className="text-muted small mb-3">
					{__(
						'This usually takes a minute or two. You can close the modal — the chapters will be here when you come back.',
						'infinite-uploads'
					)}
				</p>
			)}

			<Button
				variant="primary"
				onClick={onSave}
				disabled={!dirty || saving || polling}
			>
				{saving ? (
					<>
						<Spinner
							animation="border"
							size="sm"
							className="me-2"
						/>
						{__('Saving…', 'infinite-uploads')}
					</>
				) : (
					__('Save Chapters', 'infinite-uploads')
				)}
			</Button>
		</div>
	);
}

// ---------------------------------------------------------------------------
// Row component
// ---------------------------------------------------------------------------

function ChapterRow({index, chapter, videoLength, disabled, onChange, onRemove}) {
	const [startStr, setStartStr] = useState(formatTime(chapter.start));
	const [endStr, setEndStr] = useState(formatTime(chapter.end));

	// Reset local strings if the underlying chapter changes from outside
	// (Smart Chapters populate, save round-trip).
	useEffect(() => {
		setStartStr(formatTime(chapter.start));
	}, [chapter.start]);
	useEffect(() => {
		setEndStr(formatTime(chapter.end));
	}, [chapter.end]);

	function commitStart() {
		const parsed = parseTime(startStr);
		if (parsed == null) {
			setStartStr(formatTime(chapter.start));
			return;
		}
		const clamped = Math.max(
			0,
			videoLength > 0 ? Math.min(parsed, videoLength) : parsed
		);
		setStartStr(formatTime(clamped));
		if (clamped !== chapter.start) onChange({start: clamped});
	}

	function commitEnd() {
		const parsed = parseTime(endStr);
		if (parsed == null) {
			setEndStr(formatTime(chapter.end));
			return;
		}
		const clamped = Math.max(
			0,
			videoLength > 0 ? Math.min(parsed, videoLength) : parsed
		);
		setEndStr(formatTime(clamped));
		if (clamped !== chapter.end) onChange({end: clamped});
	}

	return (
		<div className="iup-chapter-row d-flex align-items-center gap-2 mb-2">
			<span className="text-muted small" style={{width: 20}}>
				{index}
			</span>
			<InputGroup style={{maxWidth: 110}}>
				<Form.Control
					type="text"
					value={startStr}
					onChange={(e) => setStartStr(e.target.value)}
					onBlur={commitStart}
					placeholder="00:00"
					disabled={disabled}
					aria-label={__('Chapter start time', 'infinite-uploads')}
				/>
			</InputGroup>
			<span className="text-muted small">
				{__('to', 'infinite-uploads')}
			</span>
			<InputGroup style={{maxWidth: 110}}>
				<Form.Control
					type="text"
					value={endStr}
					onChange={(e) => setEndStr(e.target.value)}
					onBlur={commitEnd}
					placeholder="00:00"
					disabled={disabled}
					aria-label={__('Chapter end time', 'infinite-uploads')}
				/>
			</InputGroup>
			<Form.Control
				type="text"
				value={chapter.title}
				onChange={(e) => onChange({title: e.target.value})}
				placeholder={__('Title', 'infinite-uploads')}
				disabled={disabled}
				className="flex-grow-1"
				aria-label={__('Chapter title', 'infinite-uploads')}
			/>
			<Button
				variant="link"
				className="text-muted p-1"
				onClick={onRemove}
				disabled={disabled}
				aria-label={__('Remove chapter', 'infinite-uploads')}
				title={__('Remove chapter', 'infinite-uploads')}
			>
				<span className="dashicons dashicons-no-alt"></span>
			</Button>
		</div>
	);
}

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

function normalize(chapters) {
	return (chapters || [])
		.map((c) => ({
			title: (c && c.title) || '',
			start: Math.max(0, Math.floor(Number(c && c.start) || 0)),
			end: Math.max(0, Math.floor(Number(c && c.end) || 0)),
		}))
		.sort((a, b) => a.start - b.start);
}

function formatTime(seconds) {
	if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) return '00:00';
	const s = Math.floor(seconds);
	const h = Math.floor(s / 3600);
	const m = Math.floor((s % 3600) / 60);
	const sec = s % 60;
	const pad = (n) => (n < 10 ? '0' + n : '' + n);
	return h > 0 ? `${h}:${pad(m)}:${pad(sec)}` : `${pad(m)}:${pad(sec)}`;
}

/**
 * Parse mm:ss, h:mm:ss, hh:mm:ss, or a bare number into seconds. Returns
 * null when the input is not a recognizable time.
 */
function parseTime(str) {
	if (typeof str !== 'string') return null;
	const trimmed = str.trim();
	if (trimmed === '') return null;
	if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10);
	const parts = trimmed.split(':').map((p) => p.trim());
	if (parts.length < 2 || parts.length > 3) return null;
	if (!parts.every((p) => /^\d+$/.test(p))) return null;
	const nums = parts.map((p) => parseInt(p, 10));
	if (nums.length === 2) {
		const [m, s] = nums;
		if (s >= 60) return null;
		return m * 60 + s;
	}
	const [h, m, s] = nums;
	if (m >= 60 || s >= 60) return null;
	return h * 3600 + m * 60 + s;
}

/**
 * Pull a display message out of a wp_send_json_error response.
 *
 * WordPress serializes the payload three different ways depending on what the
 * handler passed to wp_send_json_error():
 *   - a raw string           → `data.data === "message"`
 *   - a plain object/array   → `data.data.message === "message"`
 *   - a WP_Error             → `data.data` is an ARRAY of { code, message, data }
 * The third one is the one that was being missed and surfacing as the generic
 * "Could not start chapter generation." fallback.
 */
function extractError(data) {
	if (!data || data.data === undefined || data.data === null) return null;
	if (typeof data.data === 'string') return data.data;
	if (Array.isArray(data.data)) {
		const first = data.data.find((e) => e && typeof e.message === 'string');
		return first ? first.message : null;
	}
	if (typeof data.data.message === 'string') return data.data.message;
	return null;
}
