import { useEffect, useMemo, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import type {
	ColumnDef,
	OnChangeFn,
	PaginationState,
	RowSelectionState,
	VisibilityState,
} from "@tanstack/react-table";

import { DataTable } from "@/components/ui/data-table";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Progress } from "@/components/ui/progress";
import {
	Select,
	SelectContent,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";

import {
	useMediaStatus,
	useMediaQuery as useMediaDataQuery,
	useBuildIndex,
	useStopBuildIndex,
	useForceStopBuildIndex,
} from "../hooks/useMedia";
import { usePersistentState } from "../hooks/usePersistentState";
import type { MediaItem } from "../types";
import { useServiceInstances } from "../hooks/useServices";
import { useCounts, useLibraries } from "../hooks/useDashboard";

function formatDuration(seconds: number | null | undefined): string {
	if (seconds == null || Number.isNaN(seconds)) return "-";
	const total = Math.max(0, Math.round(seconds));
	const hours = Math.floor(total / 3600);
	const minutes = Math.floor((total % 3600) / 60);
	const secs = total % 60;
	if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
	if (minutes > 0) return `${minutes}m ${secs}s`;
	return `${secs}s`;
}

// Design §3.2 + §3.4: the 15 locked media columns. Module-level constant so the
// TanStack table instance stays stable — an unstable columns array drops the
// controlled selection/visibility state (7a discovery). Visibility-only parity
// (design §3.3): NO sorting, NO sizing/resizing is wired anywhere.
const mediaColumns: ColumnDef<MediaItem>[] = [
	{ accessorKey: "title", header: "Title" },
	{ accessorKey: "series", header: "Series" },
	{ accessorKey: "season", header: "Season" },
	{ accessorKey: "episode", header: "Episode" },
	{ accessorKey: "type", header: "Type" },
	{ accessorKey: "year", header: "Year" },
	{ accessorKey: "runtime_min", header: "Runtime" },
	{ accessorKey: "size", header: "Size" },
	{ accessorKey: "bitrate", header: "Bitrate" },
	{ accessorKey: "hdr", header: "HDR" },
	{ accessorKey: "video", header: "Video codec" },
	{ accessorKey: "resolution", header: "Resolution" },
	{ accessorKey: "date_added", header: "Date added" },
	{ accessorKey: "library", header: "Library" },
	{ accessorKey: "path", header: "Path" },
];

// Stable path-derived identity so row selection survives server-driven paging
// (design §3.4): the id is the item's filesystem path, which is stable across
// limit/offset page changes.
function getMediaRowId(row: MediaItem): string {
	return row.path;
}

const MEDIA_TAB_STATE_KEY = "manage.media.tabState";
const SMALL_BREAKPOINT = "(max-width: 900px)";
// Mirrors the pre-rework DataGrid `columnVisibilityModel` mobile override.
const MOBILE_HIDDEN_COLUMNS = [
	"series",
	"season",
	"episode",
	"bitrate",
	"video",
	"resolution",
	"date_added",
	"library",
	"path",
];

type MediaTabState = {
	search: string;
	types: string;
	hdrFilter: string;
	sortKey: string;
	sortOrder: string;
	offset: number;
	pageSize: number;
	columnVisibility: Record<string, boolean>;
};

function defaultMediaTabState(): MediaTabState {
	return {
		search: "",
		types: "Movie,Episode",
		hdrFilter: "All",
		sortKey: "title",
		sortOrder: "Ascending",
		offset: 0,
		pageSize: 100,
		columnVisibility: {},
	};
}

function usePrefersSmallScreen(): boolean {
	const supportsMatchMedia =
		typeof window !== "undefined" && typeof window.matchMedia === "function";
	const [small, setSmall] = useState(() =>
		supportsMatchMedia ? window.matchMedia(SMALL_BREAKPOINT).matches : false,
	);
	useEffect(() => {
		if (!supportsMatchMedia) return;
		const mql = window.matchMedia(SMALL_BREAKPOINT);
		const onChange = () => setSmall(mql.matches);
		mql.addEventListener("change", onChange);
		return () => mql.removeEventListener("change", onChange);
	}, [supportsMatchMedia]);
	return small;
}

function FilterSelect({
	id,
	label,
	value,
	onChange,
	options,
}: {
	id: string;
	label: string;
	value: string;
	onChange: (value: string) => void;
	options: { value: string; label: string }[];
}) {
	return (
		<div className="flex flex-col gap-1.5">
			<Label htmlFor={id}>{label}</Label>
			<Select value={value} onValueChange={onChange}>
				<SelectTrigger id={id} className="w-full">
					<SelectValue />
				</SelectTrigger>
				<SelectContent>
					{options.map((option) => (
						<SelectItem key={option.value} value={option.value}>
							{option.label}
						</SelectItem>
					))}
				</SelectContent>
			</Select>
		</div>
	);
}

// LinearProgress → Progress: determinate value drives the shadcn Progress; the
// indeterminate (null) case renders a pulsing bar, preserving the pre-rework
// "indeterminate" affordance for unknown build progress.
function BuildProgress({ value }: { value: number | null }) {
	if (value == null) {
		return (
			<div className="h-1 w-full animate-pulse rounded-full bg-muted-foreground/30" />
		);
	}
	return <Progress value={Math.max(0, Math.min(100, value * 100))} />;
}

export function Media() {
	const navigate = useNavigate();
	const [searchParams, setSearchParams] = useSearchParams();
	const isSmall = usePrefersSmallScreen();
	const { data: jellyfinServices = [] } = useServiceInstances("jellyfin");
	const selectedServiceId =
		searchParams.get("jellyfin_service_id") ||
		jellyfinServices.find((s) => s.enabled)?.id ||
		"";
	const { data: counts } = useCounts(selectedServiceId || undefined);
	const { data: libraries } = useLibraries(selectedServiceId || undefined);
	const { data: status } = useMediaStatus(selectedServiceId || undefined);
	const buildIndex = useBuildIndex(selectedServiceId || undefined);
	const stopBuildIndex = useStopBuildIndex(selectedServiceId || undefined);
	const forceStopBuildIndex = useForceStopBuildIndex(
		selectedServiceId || undefined,
	);

	const [rawMediaState, setMediaState] = usePersistentState<MediaTabState>(
		MEDIA_TAB_STATE_KEY,
		defaultMediaTabState,
	);
	// Backward-compat: merge defaults so older persisted state (pre-7b shape,
	// without pageSize/columnVisibility) never yields undefined fields.
	const mediaState: MediaTabState = {
		...defaultMediaTabState(),
		...rawMediaState,
	};
	const { search, types, hdrFilter, sortKey, sortOrder, offset, pageSize } =
		mediaState;
	const updateMediaState = (patch: Partial<MediaTabState>) =>
		setMediaState((current) => ({ ...current, ...patch }));

	const [rowSelection, setRowSelection] = useState<RowSelectionState>({});

	useEffect(() => {
		if (!searchParams.get("jellyfin_service_id") && selectedServiceId) {
			setSearchParams(
				(current) => {
					const next = new URLSearchParams(current);
					next.set("jellyfin_service_id", selectedServiceId);
					return next;
				},
				{ replace: true },
			);
		}
	}, [searchParams, selectedServiceId, setSearchParams]);

	const { data: queryResult, isLoading } = useMediaDataQuery({
		types,
		search,
		hdr_filter: hdrFilter,
		sort_key: sortKey,
		sort_order: sortOrder,
		limit: pageSize,
		offset,
		jellyfinServiceId: selectedServiceId || undefined,
		enabled: status?.exists ?? false,
	});

	// Server-driven pagination (design §3.4): pageIndex/pageSize lift into the
	// persistent media state and drive useMediaQuery { limit, offset }.
	const pageIndex = Math.floor(offset / pageSize);
	const pagination: PaginationState = { pageIndex, pageSize };

	const handlePaginationChange: OnChangeFn<PaginationState> = (updater) => {
		const next =
			typeof updater === "function"
				? updater({ pageIndex, pageSize })
				: updater;
		const nextPageSize = next.pageSize || pageSize;
		// Restart at page 0 whenever the page size changes (keeps offset sane
		// under server-driven paging).
		const nextOffset =
			nextPageSize !== pageSize ? 0 : next.pageIndex * nextPageSize;
		setMediaState((current) => ({
			...current,
			offset: nextOffset,
			pageSize: nextPageSize,
		}));
	};

	const handleColumnVisibilityChange: OnChangeFn<VisibilityState> = (
		updater,
	) => {
		setMediaState((current) => {
			const prev = current.columnVisibility ?? {};
			const next = typeof updater === "function" ? updater(prev) : updater;
			return { ...current, columnVisibility: next };
		});
	};

	// On small screens force the same set of columns hidden as the pre-rework
	// DataGrid `columnVisibilityModel` mobile override; on desktop the user
	// toggles freely (the toggleable set still equals the locked 15).
	const effectiveColumnVisibility = useMemo(() => {
		const base = mediaState.columnVisibility ?? {};
		if (!isSmall) return base;
		const merged = { ...base };
		for (const key of MOBILE_HIDDEN_COLUMNS) merged[key] = false;
		return merged;
	}, [mediaState.columnVisibility, isSmall]);

	// Preserved exactly from the DataGrid onRowClick: opens the file browser at
	// the item's path.
	const handleRowClick = (row: MediaItem) => {
		navigate(`/files?path=${encodeURIComponent(row.path)}`);
	};

	const total = queryResult?.total ?? 0;
	const totalPages = queryResult ? Math.max(1, Math.ceil(total / pageSize)) : 1;

	const buildRunning = status?.build_running ?? false;
	const buildProgress = status?.build_progress ?? null;
	const buildLibraryProgress = status?.build_library_progress ?? null;
	const buildCancelRequested = status?.build_cancel_requested ?? false;
	const buildLabel = buildRunning
		? status?.build_message || "Building media index..."
		: status?.build_error
			? `Build failed: ${status.build_error}`
			: "";
	const elapsedLabel = formatDuration(status?.build_elapsed_seconds);
	const etaLabel =
		buildRunning && status?.build_eta_seconds != null
			? formatDuration(status.build_eta_seconds)
			: "-";
	const libraryElapsedLabel = formatDuration(
		status?.build_library_elapsed_seconds,
	);
	const libraryEtaLabel =
		buildRunning && status?.build_library_eta_seconds != null
			? formatDuration(status.build_library_eta_seconds)
			: "-";
	const libraryLabel =
		status?.build_current_library ||
		(status?.build_library_index && status?.build_libraries_total
			? `Library ${status.build_library_index} / ${status.build_libraries_total}`
			: "Current library");

	return (
		<div className="flex flex-col gap-4">
			<div className="flex flex-row flex-wrap items-center gap-2">
				<h2 className="text-lg font-semibold">Jellyfin</h2>
				<div className="flex flex-col gap-1.5">
					<Label htmlFor="media-service">Service</Label>
					<Select
						value={selectedServiceId}
						onValueChange={(value) =>
							setSearchParams(
								(current) => {
									const next = new URLSearchParams(current);
									next.set("jellyfin_service_id", value);
									return next;
								},
								{ replace: true },
							)
						}
					>
						<SelectTrigger id="media-service" className="w-full md:w-[220px]">
							<SelectValue placeholder="Select a service" />
						</SelectTrigger>
						<SelectContent>
							{jellyfinServices.map((service) => (
								<SelectItem key={service.id} value={service.id}>
									{service.name}
								</SelectItem>
							))}
						</SelectContent>
					</Select>
				</div>

				{status?.exists ? (
					<p className="text-sm text-muted-foreground">
						Index: {status.item_count.toLocaleString()} items
						{status.updated_at_label
							? ` | updated ${status.updated_at_label}`
							: ""}
					</p>
				) : (
					<Alert variant="destructive" className="py-0">
						<AlertDescription>No index built yet.</AlertDescription>
					</Alert>
				)}

				{counts && (
					<p className="text-sm text-muted-foreground">
						Library stats: {counts.movies.toLocaleString()} movies ·{" "}
						{counts.series.toLocaleString()} series ·{" "}
						{counts.episodes.toLocaleString()} episodes ·{" "}
						{(libraries?.length ?? 0).toLocaleString()} libraries
					</p>
				)}

				<Button
					variant="outline"
					onClick={() => buildIndex.mutate()}
					disabled={
						buildIndex.isPending || buildRunning || buildCancelRequested
					}
				>
					{buildIndex.isPending || buildRunning ? "Building..." : "Build index"}
				</Button>
				{buildRunning && (
					<>
						<Button
							variant="destructive"
							onClick={() => stopBuildIndex.mutate()}
							disabled={stopBuildIndex.isPending || buildCancelRequested}
						>
							{buildCancelRequested || stopBuildIndex.isPending
								? "Stopping..."
								: "Stop build"}
						</Button>
						<Button
							variant="outline"
							className="border-chart-3/40 text-chart-3 hover:bg-chart-3/10"
							onClick={() => forceStopBuildIndex.mutate()}
							disabled={forceStopBuildIndex.isPending}
						>
							{forceStopBuildIndex.isPending
								? "Force stopping..."
								: "Force stop"}
						</Button>
					</>
				)}
			</div>

			{(buildRunning || status?.build_error) && (
				<div className="flex w-full min-w-[260px] flex-col gap-2">
					<p
						className={
							status?.build_error
								? "text-sm text-destructive"
								: "text-sm text-muted-foreground"
						}
					>
						{buildLabel ||
							(buildRunning
								? "Building media index..."
								: status?.build_error || "")}
					</p>

					<div className="flex flex-col gap-1">
						<p className="text-xs text-muted-foreground">
							Overall:{" "}
							{buildProgress != null
								? `${Math.round(buildProgress * 100)}%`
								: "pending"}
							{buildRunning
								? ` • elapsed ${elapsedLabel} • eta ${etaLabel}`
								: ""}
						</p>
						<BuildProgress value={buildProgress} />
						<p className="text-xs text-muted-foreground">
							{status?.build_items_processed?.toLocaleString() ?? 0}/
							{status?.build_items_total?.toLocaleString() ?? 0} items
						</p>
					</div>

					<div className="flex flex-col gap-1">
						<p className="text-xs text-muted-foreground">
							Current: {libraryLabel}
							{buildRunning
								? ` • elapsed ${libraryElapsedLabel} • eta ${libraryEtaLabel}`
								: ""}
						</p>
						<BuildProgress value={buildLibraryProgress} />
						<p className="text-xs text-muted-foreground">
							{status?.build_library_items_processed?.toLocaleString() ?? 0}/
							{status?.build_library_items_total?.toLocaleString() ?? 0} items
						</p>
					</div>
				</div>
			)}

			<Card>
				<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-12">
					<div className="col-span-1 flex flex-col gap-1.5 md:col-span-4">
						<Label htmlFor="media-search">Search</Label>
						<Input
							id="media-search"
							value={search}
							onChange={(e) =>
								updateMediaState({ search: e.target.value, offset: 0 })
							}
						/>
					</div>
					<div className="col-span-6 md:col-span-2">
						<FilterSelect
							id="media-types"
							label="Types"
							value={types}
							onChange={(value) =>
								updateMediaState({ types: value, offset: 0 })
							}
							options={[
								{ value: "Movie,Episode", label: "Movies + Episodes" },
								{ value: "Movie", label: "Movies only" },
								{ value: "Episode", label: "Episodes only" },
								{ value: "Movie,Episode,Video", label: "All video" },
							]}
						/>
					</div>
					<div className="col-span-6 md:col-span-2">
						<FilterSelect
							id="media-hdr"
							label="HDR"
							value={hdrFilter}
							onChange={(value) =>
								updateMediaState({ hdrFilter: value, offset: 0 })
							}
							options={[
								{ value: "All", label: "All" },
								{ value: "HDR only", label: "HDR only" },
								{ value: "SDR/unknown only", label: "SDR/unknown only" },
							]}
						/>
					</div>
					<div className="col-span-6 md:col-span-2">
						<FilterSelect
							id="media-sort"
							label="Sort"
							value={sortKey}
							onChange={(value) => updateMediaState({ sortKey: value })}
							options={[
								{ value: "title", label: "Title" },
								{ value: "series", label: "Series" },
								{ value: "size", label: "Size" },
								{ value: "bitrate", label: "Bitrate" },
								{ value: "runtime", label: "Runtime" },
								{ value: "year", label: "Year" },
								{ value: "date_added", label: "Date added" },
								{ value: "resolution", label: "Resolution" },
							]}
						/>
					</div>
					<div className="col-span-6 md:col-span-2">
						<FilterSelect
							id="media-order"
							label="Order"
							value={sortOrder}
							onChange={(value) => updateMediaState({ sortOrder: value })}
							options={[
								{ value: "Ascending", label: "Ascending" },
								{ value: "Descending", label: "Descending" },
							]}
						/>
					</div>
				</CardContent>
			</Card>

			{queryResult && (
				<p className="text-xs text-muted-foreground">
					Showing {queryResult.items.length} of {total.toLocaleString()} items |
					Page {pageIndex + 1} of {totalPages}
				</p>
			)}

			{status?.exists && (
				<div className="rounded-lg border bg-card">
					<DataTable
						columns={mediaColumns}
						data={queryResult?.items ?? []}
						getRowId={getMediaRowId}
						enableRowSelection
						rowSelection={rowSelection}
						onRowSelectionChange={setRowSelection}
						onRowClick={handleRowClick}
						enableColumnVisibilityToggle
						columnVisibility={effectiveColumnVisibility}
						onColumnVisibilityChange={handleColumnVisibilityChange}
						enablePagination
						manualPagination
						pagination={pagination}
						onPaginationChange={handlePaginationChange}
						pageSizeOptions={[50, 100, 200]}
						rowCount={total}
						emptyMessage={
							isLoading
								? "Loading media..."
								: "No media items match these filters."
						}
					/>
				</div>
			)}
		</div>
	);
}
