/**
 * Content Types Component.
 *
 * Manage post type selection for indexing.
 * Allows updating content type selections anytime.
 *
 * @package Sideconvo
 */

import { useState, useEffect, useRef, useMemo } from '@wordpress/element';
import {
	Button,
	AlertBar,
	EmptyState,
	Input,
	PageHeader,
	SelectFilter,
	IconSearch,
	IconArrowDown,
} from '../ui';
import type { MenuSelectItem } from '../ui';
import { useSettings } from '../../contexts/SettingsContext';
import DeletePostTypeModal from '../modals/DeletePostTypeModal';
import styles from './ContentTypes.module.scss';

// ── Types ──────────────────────────────────────────

interface ContentType {
	name: string;
	label: string;
	count: number;
	disabled?: boolean;
}

type FilterMode = 'all' | 'included' | 'excluded';

interface PendingUncheck {
	name: string;
	label: string;
	indexedCount: number;
	kind: 'post_type' | 'taxonomy';
}

// ── Component ──────────────────────────────────────

export default function ContentTypes() {
	const { showNotification } = useSettings();
	const [postTypes, setPostTypes] = useState<ContentType[]>([]);
	const [selectedPostTypes, setSelectedPostTypes] = useState<string[]>([]);
	const [taxonomies, setTaxonomies] = useState<ContentType[]>([]);
	const [selectedTaxonomies, setSelectedTaxonomies] = useState<string[]>([]);
	const [isLoading, setIsLoading] = useState(true);
	const [isSaving, setIsSaving] = useState(false);
	// Mirror of what the server currently has saved — used to detect pending
	// changes so the Save button stays enabled while the user is editing,
	// but disabled once the server state is already empty (nothing to save).
	const [savedPostTypes, setSavedPostTypes] = useState<string[]>([]);
	const [savedTaxonomies, setSavedTaxonomies] = useState<string[]>([]);
	const [searchQuery, setSearchQuery] = useState('');
	const [filterMode, setFilterMode] = useState<FilterMode>('all');
	const [filterOpen, setFilterOpen] = useState(false);
	const [indexedByType, setIndexedByType] = useState<Record<string, number>>({});
	const [pendingUncheck, setPendingUncheck] = useState<PendingUncheck | null>(null);
	// Types the user confirmed should be deleted from Sideconvo on next Save.
	const [typesToDelete, setTypesToDelete] = useState<Array<{ name: string; kind: 'post_type' | 'taxonomy' }>>([]);
	const listRef = useRef<HTMLDivElement>(null);
	const [canScroll, setCanScroll] = useState(false);
	// Cancel function for the background sync polling loop started after save.
	const cancelBackgroundSyncRef = useRef<(() => void) | null>(null);
	// Cancel function for the background delete polling loop started after save.
	const cancelBackgroundDeleteRef = useRef<(() => void) | null>(null);

	// ── Effects ────────────────────────────────────

	useEffect(() => {
		fetchContentTypes();
		fetchSyncStats();
	}, []);

	// Detect if list is scrollable
	useEffect(() => {
		const el = listRef.current;
		if (!el) return;
		const check = () => setCanScroll(el.scrollHeight > el.clientHeight);
		check();
		const ro = new ResizeObserver(check);
		ro.observe(el);
		return () => ro.disconnect();
	}, [postTypes, taxonomies]);

	// ── API Functions ──────────────────────────────

	const fetchContentTypes = async ( attempt = 1 ) => {
		try {
			const response = await fetch(
				`${(window as any).sideconvoData.restUrl}sideconvo/v1/content-types`,
				{
					headers: {
						'X-WP-Nonce': (window as any).sideconvoData.nonce,
					},
					cache: 'no-store',
				}
			);

			if (!response.ok) {
				throw new Error('Failed to fetch content types');
			}

			const data = await response.json();

			// If we got an empty post-type list this is almost certainly a transient
			// server-side issue (DB lock from a concurrent query, WPML language context
			// not yet initialised, etc.). Retry once after a short delay before giving up.
			// Keep isLoading=true so the skeleton stays visible during the retry window.
			if ( attempt === 1 && ( !data.postTypes || data.postTypes.length === 0 ) ) {
				setTimeout( () => fetchContentTypes( 2 ), 800 );
				return; // do NOT call setIsLoading(false) — skeleton must stay shown
			}

			setPostTypes(data.postTypes || []);
			setTaxonomies(data.taxonomies || []);
			setSelectedTaxonomies(data.selectedTaxonomies || []);
			setSelectedPostTypes(data.selectedPostTypes || []);
			setSavedPostTypes(data.selectedPostTypes || []);
			setSavedTaxonomies(data.selectedTaxonomies || []);
			setIsLoading(false);
		} catch (error) {
			console.error('Error fetching content types:', error);
			showNotification('error', 'Failed to load content types');
			setIsLoading(false);
		}
	};

	const fetchSyncStats = async () => {
		try {
			const response = await fetch(
				`${(window as any).sideconvoData.restUrl}sideconvo/v1/sync-stats`,
				{
					headers: {
						'X-WP-Nonce': (window as any).sideconvoData.nonce,
					},
					cache: 'no-store',
				}
			);
			if (!response.ok) return;
			const data = await response.json();
			setIndexedByType(data.by_type || {});
		} catch {
			// Non-critical — silently ignore
		}
	};

	const togglePostType = (name: string) => {
		const postType = postTypes.find((pt) => pt.name === name);
		if (postType?.disabled) return;

		const isCurrentlyChecked = selectedPostTypes.includes(name);

		if (!isCurrentlyChecked) {
			// Checking: toggle immediately.
			setSelectedPostTypes((prev) => [...prev, name]);
			return;
		}

		// Unchecking: check if there are indexed records.
		const indexed = indexedByType[name] ?? 0;
		if (indexed > 0) {
			setPendingUncheck({
				name,
				label: postType?.label ?? name,
				indexedCount: indexed,
				kind: 'post_type',
			});
		} else {
			setSelectedPostTypes((prev) => prev.filter((item) => item !== name));
		}
	};

	const handleModalDelete = () => {
		if (!pendingUncheck) return;
		const { name, kind } = pendingUncheck;
		setPendingUncheck(null);
		// Deselect the type and mark it for deletion on Save — don't call the
		// delete API yet so the user can still adjust other selections first.
		setTypesToDelete((prev) => [...prev, { name, kind }]);
		setIndexedByType((prev) => ({ ...prev, [name]: 0 }));
		if (kind === 'taxonomy') {
			setSelectedTaxonomies((prev) => prev.filter((t) => t !== name));
		} else {
			setSelectedPostTypes((prev) => prev.filter((item) => item !== name));
		}
	};

	const handleModalKeep = () => {
		if (!pendingUncheck) return;
		const { name, kind } = pendingUncheck;
		setPendingUncheck(null);
		if (kind === 'taxonomy') {
			setSelectedTaxonomies((prev) => prev.filter((t) => t !== name));
		} else {
			setSelectedPostTypes((prev) => prev.filter((item) => item !== name));
		}
	};

	const handleModalDismiss = () => {
		setPendingUncheck(null);
		// Checkbox stays checked — no state change to selectedPostTypes.
	};

	const toggleTaxonomy = (name: string) => {
		const isCurrentlyChecked = selectedTaxonomies.includes(name);

		if (!isCurrentlyChecked) {
			setSelectedTaxonomies((prev) => [...prev, name]);
			return;
		}

		// Unchecking: show confirmation if there are indexed records.
		const indexed = indexedByType[name] ?? 0;
		if (indexed > 0) {
			const taxonomy = taxonomies.find((t) => t.name === name);
			setPendingUncheck({
				name,
				label: taxonomy?.label ?? name,
				indexedCount: indexed,
				kind: 'taxonomy',
			});
		} else {
			setSelectedTaxonomies((prev) => prev.filter((t) => t !== name));
		}
	};

	// Tracks whether the component is still mounted. Prevents the background
	// delete loop from starting inside a Promise.allSettled .then() that fires
	// after the user has navigated away (SC-1044).
	const isMountedRef = useRef(true);

	// Cancel any in-flight background sync when component unmounts (e.g. user
	// navigates away) so the polling loop does not run indefinitely.
	useEffect(() => {
		isMountedRef.current = true;
		return () => {
			isMountedRef.current = false;
			cancelBackgroundSyncRef.current?.();
			cancelBackgroundDeleteRef.current?.();
		};
	}, []);

	/**
	 * Start a background batch-processing loop without blocking the UI.
	 * Polls process-batch every 5 s until the server reports completion.
	 * Returns a cancel function to stop the loop early.
	 */
	const runBackgroundSync = () => {
		let cancelled = false;
		let attempts = 0;
		const MAX_ATTEMPTS = 200; // ~16 min safety ceiling

		const tick = async () => {
			if (cancelled || attempts >= MAX_ATTEMPTS) return;
			attempts++;
			try {
				const res = await fetch(
					`${(window as any).sideconvoData.restUrl}sideconvo/v1/process-batch`,
					{
						method: 'POST',
						headers: {
							'Content-Type': 'application/json',
							'X-WP-Nonce': (window as any).sideconvoData.nonce,
						},
					}
				);
				if (!res.ok) return;
				const data = await res.json().catch(() => null);
				if (!data) return;
				const done =
					data.complete ||
					data.status === 'completed' ||
					data.total === 0 ||
					(data.total > 0 && data.processed >= data.total);
				// Notify the Sidebar to refresh its stats on every batch.
				window.dispatchEvent(new CustomEvent('sideconvo:batch-processed'));
				if (done) {
					cancelled = true;
					window.dispatchEvent(new CustomEvent('sideconvo:content-types-synced'));
				} else if (!cancelled) {
					setTimeout(tick, 5000);
				}
			} catch {
				// Non-critical — background sync failure is silent
			}
		};

		tick();
		return () => { cancelled = true; };
	};

	// Polls process-delete-batch until all queued_for_delete items are processed (SC-1002).
	const runBackgroundDelete = () => {
		let cancelled = false;
		let attempts = 0;
		const MAX_ATTEMPTS = 200; // ~16 min safety ceiling

		const tick = async () => {
			if (cancelled || attempts >= MAX_ATTEMPTS) return;
			attempts++;
			try {
				const res = await fetch(
					`${(window as any).sideconvoData.restUrl}sideconvo/v1/process-delete-batch`,
					{
						method: 'POST',
						headers: {
							'Content-Type': 'application/json',
							'X-WP-Nonce': (window as any).sideconvoData.nonce,
						},
					}
				);
				if (!res.ok) return;
				const data = await res.json().catch(() => null);
				if (!data) return;
				const done = data.complete || data.total === 0;
				if (done) {
					cancelled = true;
					try { localStorage.removeItem('sideconvo_reconciliation_report'); } catch { /* non-critical */ }
					window.dispatchEvent(new CustomEvent('sideconvo:content-types-deleted'));
				} else if (!cancelled) {
					setTimeout(tick, 5000);
				}
			} catch {
				// Non-critical — background delete failure is silent
			}
		};

		tick();
		return () => { cancelled = true; };
	};

	const handleSave = async () => {
		setIsSaving(true);

		try {
			const response = await fetch(
				`${(window as any).sideconvoData.restUrl}sideconvo/v1/content-types`,
				{
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
						'X-WP-Nonce': (window as any).sideconvoData.nonce,
					},
					body: JSON.stringify({
						post_types: selectedPostTypes,
						taxonomies: selectedTaxonomies,
					}),
				}
			);

			if (!response.ok) {
				throw new Error('Failed to save content types');
			}

			const saveData = await response.json();

			showNotification('success', 'Content types updated successfully');
			setSavedPostTypes(selectedPostTypes);
			setSavedTaxonomies(selectedTaxonomies);

			// If new post types or taxonomies were queued for sync, notify any mounted
			// screens (e.g. Sync page) so they refresh stats and start processing.
			const hasNewlyQueued =
				(saveData?.queued_post_types?.length ?? 0) > 0 ||
				(saveData?.queued_taxonomies?.length ?? 0) > 0;
			if (hasNewlyQueued) {
				// Cancel any previous loop before starting a new one.
				cancelBackgroundSyncRef.current?.();
				cancelBackgroundSyncRef.current = runBackgroundSync();
				try { } catch { /* non-critical */ }
				window.dispatchEvent(new CustomEvent('sideconvo:content-types-added'));
			}

			// Fire pending deletions in the background after the selection is saved
			// so the backend exclusion list is up-to-date. Each call marks items as
			// queued_for_delete and returns immediately; the polling loop then processes
			// them in batches to avoid PHP timeout on large sites (SC-1002).
			const pending = typesToDelete;
			setTypesToDelete([]);
			if (pending.length > 0) {
				// Cancel any prior delete loop before starting a new one.
				cancelBackgroundDeleteRef.current?.();

				Promise.allSettled(
					pending.map(({ name, kind }) => {
						const url =
							kind === 'taxonomy'
								? `${(window as any).sideconvoData.restUrl}sideconvo/v1/delete-taxonomy-content`
								: `${(window as any).sideconvoData.restUrl}sideconvo/v1/delete-post-type-content`;
						const body =
							kind === 'taxonomy'
								? JSON.stringify({ taxonomy: name })
								: JSON.stringify({ post_type: name });
						return fetch(url, {
							method: 'POST',
							headers: {
								'Content-Type': 'application/json',
								'X-WP-Nonce': (window as any).sideconvoData.nonce,
							},
							body,
						});
					})
				).then(() => {
					// All items are now marked queued_for_delete — start the batch polling loop.
					// Guard: if the user navigated away while the delete fetches were in-flight,
					// do not start the polling loop (SC-1044).
					if (!isMountedRef.current) return;
					cancelBackgroundDeleteRef.current = runBackgroundDelete();
				});
			}
		} catch (error) {
			console.error('Error saving content types:', error);
			showNotification('error', 'Failed to save content types');
		} finally {
			setIsSaving(false);
		}
	};

	// ── Derived Data ───────────────────────────────

	const FILTER_ITEMS: MenuSelectItem[] = [
		{ value: 'all', label: 'Show All' },
		{ value: 'included', label: 'Included' },
		{ value: 'excluded', label: 'Excluded' },
	];

	const filterLabel = filterMode === 'all'
		? 'Show All'
		: filterMode === 'included'
			? 'Included'
			: 'Excluded';

	const handleFilterChange = (value: string) => {
		setFilterMode(value as FilterMode);
	};

	const filteredTypes = useMemo(() => {
		return postTypes.filter((pt) => {
			const matchesSearch = pt.label.toLowerCase().includes(searchQuery.toLowerCase());
			const isSelected = selectedPostTypes.includes(pt.name);
			if (filterMode === 'included') return matchesSearch && isSelected;
			if (filterMode === 'excluded') return matchesSearch && !isSelected;
			return matchesSearch;
		});
	}, [postTypes, searchQuery, filterMode, selectedPostTypes]);

	const filteredTaxonomies = useMemo(() => {
		return taxonomies.filter((tax) => {
			const matchesSearch = tax.label.toLowerCase().includes(searchQuery.toLowerCase());
			const isSelected = selectedTaxonomies.includes(tax.name);
			if (filterMode === 'included') return matchesSearch && isSelected;
			if (filterMode === 'excluded') return matchesSearch && !isSelected;
			return matchesSearch;
		});
	}, [taxonomies, searchQuery, filterMode, selectedTaxonomies]);

	// ── Render ─────────────────────────────────────

	return (
		<div className={styles.page}>
			{/* ── Delete confirmation modal ── */}
			{ pendingUncheck && (
				<DeletePostTypeModal
					isOpen={ true }
					postTypeLabel={ pendingUncheck.label }
					indexedCount={ pendingUncheck.indexedCount }
					onDelete={ handleModalDelete }
					onKeep={ handleModalKeep }
					onDismiss={ handleModalDismiss }
				/>
			) }

			{/* ── Header ── */}
			<PageHeader
				title="Content Types"
				subtitle="Select which post types to sync with Sideconvo"
				toolbar={
					<>
						<Input
							size="slim"
							leadingIcon={<IconSearch size={16} />}
							placeholder="Search"
							value={searchQuery}
							onChange={setSearchQuery}
							clearable
							className={styles.searchInput}
						/>
						<SelectFilter
							label={filterLabel}
							items={FILTER_ITEMS}
							value={filterMode}
							onChange={handleFilterChange}
							isOpen={filterOpen}
							onOpenChange={setFilterOpen}
							showBadge={false}
						/>
						<Button
							variant="primary"
							size="small"
							onClick={handleSave}
							disabled={isSaving || (selectedPostTypes.length === 0 && selectedTaxonomies.length === 0 && savedPostTypes.length === 0 && savedTaxonomies.length === 0)}
						>
							{isSaving ? 'Saving...' : 'Save Changes'}
						</Button>
					</>
				}
			/>

			{/* ── Info Bar ── */}
			<AlertBar message="Selected content types will sync new content to Sideconvo automatically." variant="info" />

			{/* ── Content Type List ── */}
			<div className={styles.listContainer}>
				<div className={styles.listHeader}>
					<span className={styles.listHeaderLabel}>Eligible for Inclusion</span>
					<span className={styles.listHeaderCount}>Items</span>
				</div>
				<div className={styles.list} ref={listRef}>
					{isLoading ? (
						<>
							{Array.from({ length: 6 }).map((_, i) => (
								<div key={i} className={styles.skeletonRow}>
									<div className={styles.skeletonCheckbox} />
									<div className={styles.skeletonCell} style={{ width: `${40 + (i % 5) * 12}%` }} />
									<div className={styles.skeletonCell} style={{ width: 64, flexShrink: 0 }} />
								</div>
							))}
						</>
					) : filteredTypes.length === 0 && filteredTaxonomies.length === 0 ? (
						<EmptyState
							size="minimal"
							title={searchQuery || filterMode !== 'all'
								? 'No content types match your filters'
								: 'No content types found'}
							subtitle={searchQuery || filterMode !== 'all'
								? 'Try adjusting your search or filter criteria'
								: undefined}
						/>
					) : (
						<>
							{filteredTypes.map((postType) => {
								const isChecked = selectedPostTypes.includes(postType.name);
								const isDisabled = postType.disabled ?? false;
								return (
									<label
										key={postType.name}
										className={`${styles.row}${isDisabled ? ` ${styles.rowDisabled}` : ''}`}
										title={isDisabled ? 'No published content — not eligible for sync' : undefined}
									>
										<input
											type="checkbox"
											className={styles.checkbox}
											checked={isChecked}
											disabled={isDisabled}
											onChange={() => togglePostType(postType.name)}
										/>
										<span className={styles.rowLabel}>{postType.label}</span>
										<span className={styles.rowCount}>{postType.count.toLocaleString()} Items</span>
									</label>
								);
							})}
							{filteredTaxonomies.length > 0 && (
								<>
									{filteredTaxonomies.map((taxonomy) => {
										const isChecked = selectedTaxonomies.includes(taxonomy.name);
										return (
											<label
												key={taxonomy.name}
												className={styles.row}
											>
												<input
													type="checkbox"
													className={styles.checkbox}
													checked={isChecked}
													onChange={() => toggleTaxonomy(taxonomy.name)}
												/>
												<span className={styles.rowLabel}>{taxonomy.label}</span>
												<span className={styles.rowCount}>{taxonomy.count.toLocaleString()} Items</span>
											</label>
										);
									})}
								</>
							)}
						</>
					)}
				</div>

				{/* Scroll indicator */}
				{canScroll && (
					<div className={styles.scrollIndicator}>
						<IconArrowDown size={14} />
						<span>Scroll for more</span>
					</div>
				)}
			</div>
		</div>
	);
}
