"use client"

import type { CutRegion, IFragmentsStepProps } from "./FragmentsStep.d"
import type { JSX } from "react"

import { MediaPlayer, MediaProvider } from "@vidstack/react"
import {
	DefaultVideoLayout,
	defaultLayoutIcons,
} from "@vidstack/react/player/layouts/default"
import "@vidstack/react/player/styles/default/theme.css"
import "@vidstack/react/player/styles/default/layouts/video.css"
import cs from "classnames"
import { Plus, Trash2 } from "lucide-react"
import {
	FunctionComponent,
	useCallback,
	useEffect,
	useMemo,
	useRef,
	useState,
} from "react"
import WaveSurfer from "wavesurfer.js"

import api from "@shared/api"
import { useProjectWorkspaceQuery } from "@shared/api/projectWorkflow"
import { useWizard } from "@shared/context/WizardContext"
import { useSegmentResize } from "@shared/hooks/useSegmentResize"
import { Button } from "@shared/ui"

import styles from "./FragmentsStep.module.scss"

const MIN_REGION_MS = 100
const DEFAULT_NEW_REGION_MS = 1000
const DEFAULT_PPS = 10
const MIN_PPS = 2
const MAX_PPS = 200
const PPS_STEP = 2
const FRAMES_HEIGHT = 48
const WAVEFORM_HEIGHT = 48
const RULER_HEIGHT = 24
const MAX_EXTRACTED_FRAMES = 150
const CANVAS_OVERSCAN = 300

let regionIdCounter = 0
const nextRegionId = (): string => `region_${++regionIdCounter}`

const formatDuration = (ms: number): string => {
	const totalSec = Math.floor(ms / 1000)
	const min = Math.floor(totalSec / 60)
	const sec = totalSec % 60
	if (min > 0) return `${min}м ${sec}с`
	return `${sec}с`
}

function resolveWaveformColors(): { wave: string; progress: string } {
	const root = getComputedStyle(document.documentElement)
	return {
		wave:
			root.getPropertyValue("--waveform-wave").trim() ||
			"hsl(297, 70%, 44%)",
		progress:
			root.getPropertyValue("--waveform-progress").trim() ||
			"hsl(293, 100%, 34%)",
	}
}

export const FragmentsStep: FunctionComponent<IFragmentsStepProps> = ({
	className,
}): JSX.Element => {
	const {
		projectId,
		primaryFileId,
		startSilenceApply,
		skipSilenceApply,
		goBack,
	} = useWizard()
	const { data: workspace } = useProjectWorkspaceQuery(projectId)

	const [cutRegions, setCutRegions] = useState<CutRegion[]>([])
	const [pixelsPerSecond, setPixelsPerSecond] = useState(DEFAULT_PPS)
	const [durationMs, setDurationMs] = useState(0)
	const [contextMenu, setContextMenu] = useState<{
		x: number
		y: number
		regionId: string | null
		timeMs: number
	} | null>(null)
	const timelineRef = useRef<HTMLDivElement>(null)
	const playerRef = useRef<any>(null)
	const waveformRef = useRef<HTMLDivElement>(null)
	const wsRef = useRef<WaveSurfer | null>(null)

	const silenceState = workspace?.silence

	const { data: fileInfo } = api.useQuery(
		"get",
		"/api/files/files/{file_id}/resolve/",
		{ params: { path: { file_id: primaryFileId ?? "" } } },
		{ enabled: !!primaryFileId },
	)

	const videoUrl = fileInfo?.file_url ?? null

	/* ---- Initialize cut regions from detection results ---- */
	useEffect(() => {
		if (!silenceState) return
		const segments =
			silenceState.reviewed_cuts.length > 0
				? silenceState.reviewed_cuts
				: silenceState.detected_segments
		const dur = silenceState.duration_ms

		if (segments && dur) {
			setDurationMs(dur)
			setCutRegions(
				segments.map((s) => ({
					id: nextRegionId(),
					startMs: s.start_ms,
					endMs: s.end_ms,
				})),
			)
		}
	}, [silenceState])

	/* ---- Timeline calculations ---- */
	const totalWidth = Math.max(1, (durationMs / 1000) * pixelsPerSecond)

	const msToPixels = useCallback(
		(ms: number) => (ms / 1000) * pixelsPerSecond,
		[pixelsPerSecond],
	)

	const pixelsToMs = useCallback(
		(px: number) => (px / pixelsPerSecond) * 1000,
		[pixelsPerSecond],
	)

	const totalRemovedMs = useMemo(
		() => cutRegions.reduce((sum, r) => sum + (r.endMs - r.startMs), 0),
		[cutRegions],
	)

	/* ---- Region mutations ---- */
	const addRegion = useCallback(
		(atMs: number) => {
			const startMs = Math.max(0, atMs - DEFAULT_NEW_REGION_MS / 2)
			const endMs = Math.min(durationMs, startMs + DEFAULT_NEW_REGION_MS)
			setCutRegions((prev) =>
				[...prev, { id: nextRegionId(), startMs, endMs }].sort(
					(a, b) => a.startMs - b.startMs,
				),
			)
		},
		[durationMs],
	)

	const removeRegion = useCallback((regionId: string) => {
		setCutRegions((prev) => prev.filter((r) => r.id !== regionId))
	}, [])

	/* ---- Resize handling ---- */
	const { handlePointerDown: handleResizePointerDown } = useSegmentResize({
		pixelsPerSecond,
		onResize: (index, edge, deltaSec) => {
			setCutRegions((prev) => {
				const updated = [...prev]
				const region = { ...updated[index] }
				const deltaMs = deltaSec * 1000

				if (edge === "left") {
					region.startMs = Math.max(
						0,
						Math.min(
							region.endMs - MIN_REGION_MS,
							region.startMs + deltaMs,
						),
					)
				} else {
					region.endMs = Math.min(
						durationMs,
						Math.max(
							region.startMs + MIN_REGION_MS,
							region.endMs + deltaMs,
						),
					)
				}
				updated[index] = region
				return updated
			})
		},
		onResizeEnd: () => {},
	})

	/* ---- Drag-to-move handling ---- */
	const handleRegionDragStart = useCallback(
		(e: React.PointerEvent, index: number) => {
			e.stopPropagation()
			const startX = e.clientX
			const region = cutRegions[index]
			const regionDuration = region.endMs - region.startMs

			const onMove = (moveE: PointerEvent) => {
				const dx = moveE.clientX - startX
				const deltaMs = pixelsToMs(dx)
				let newStart = region.startMs + deltaMs
				newStart = Math.max(
					0,
					Math.min(durationMs - regionDuration, newStart),
				)

				setCutRegions((prev) => {
					const updated = [...prev]
					updated[index] = {
						...updated[index],
						startMs: Math.round(newStart),
						endMs: Math.round(newStart + regionDuration),
					}
					return updated
				})
			}

			const onUp = () => {
				document.removeEventListener("pointermove", onMove)
				document.removeEventListener("pointerup", onUp)
			}

			document.addEventListener("pointermove", onMove)
			document.addEventListener("pointerup", onUp)
		},
		[cutRegions, durationMs, pixelsToMs],
	)

	/* ---- Context menu ---- */
	const handleContextMenu = useCallback(
		(e: React.MouseEvent, regionId: string | null) => {
			e.preventDefault()
			e.stopPropagation()

			const rect = timelineRef.current?.getBoundingClientRect()
			const scrollLeft = timelineRef.current?.scrollLeft ?? 0
			const x = e.clientX - (rect?.left ?? 0) + scrollLeft
			const timeMs = pixelsToMs(x)

			setContextMenu({ x: e.clientX, y: e.clientY, regionId, timeMs })
		},
		[pixelsToMs],
	)

	useEffect(() => {
		if (!contextMenu) return
		const close = () => setContextMenu(null)
		document.addEventListener("click", close)
		return () => document.removeEventListener("click", close)
	}, [contextMenu])

	/* ---- Timeline click to seek ---- */
	const handleTimelineClick = useCallback(
		(e: React.MouseEvent) => {
			const rect = timelineRef.current?.getBoundingClientRect()
			if (!rect) return
			const scrollLeft = timelineRef.current?.scrollLeft ?? 0
			const x = e.clientX - rect.left + scrollLeft
			const timeSec = pixelsToMs(x) / 1000

			if (playerRef.current) {
				playerRef.current.currentTime = timeSec
			}
		},
		[pixelsToMs],
	)

	/* ---- Canvas: ruler ---- */
	const rulerRef = useRef<HTMLCanvasElement>(null)

	const drawRuler = useCallback(() => {
		const container = timelineRef.current
		const canvas = rulerRef.current
		if (!container || !canvas || !durationMs) return

		const sl = container.scrollLeft
		const vw = container.clientWidth
		if (!vw) return
		const canvasW = Math.min(vw + CANVAS_OVERSCAN * 2, totalWidth)
		const offset = Math.max(
			0,
			Math.min(sl - CANVAS_OVERSCAN, totalWidth - canvasW),
		)

		const dpr = window.devicePixelRatio || 1
		canvas.width = canvasW * dpr
		canvas.height = RULER_HEIGHT * dpr
		canvas.style.width = `${canvasW}px`
		canvas.style.height = `${RULER_HEIGHT}px`
		canvas.style.transform = `translateX(${offset}px)`

		const ctx = canvas.getContext("2d")
		if (!ctx) return
		ctx.scale(dpr, dpr)
		ctx.clearRect(0, 0, canvasW, RULER_HEIGHT)

		const rootStyles = getComputedStyle(document.documentElement)
		const textColor =
			rootStyles.getPropertyValue("--text-secondary").trim() || "#888"
		const lineColor =
			rootStyles.getPropertyValue("--border-subtle").trim() || "#444"

		ctx.strokeStyle = lineColor
		ctx.fillStyle = textColor
		ctx.font = "10px monospace"
		ctx.textAlign = "center"

		const totalSec = durationMs / 1000
		let tickInterval = 1
		if (pixelsPerSecond < 5) tickInterval = 30
		else if (pixelsPerSecond < 10) tickInterval = 15
		else if (pixelsPerSecond < 20) tickInterval = 10
		else if (pixelsPerSecond < 50) tickInterval = 5
		else if (pixelsPerSecond < 150) tickInterval = 1
		else tickInterval = 0.5

		const majorMultiple = tickInterval >= 1 ? 5 : 1
		const startSec =
			Math.floor(offset / pixelsPerSecond / tickInterval) * tickInterval
		const endSec = Math.min(
			totalSec,
			(offset + canvasW) / pixelsPerSecond,
		)

		for (let sec = startSec; sec <= endSec; sec += tickInterval) {
			const x = sec * pixelsPerSecond - offset
			if (x < -20 || x > canvasW + 20) continue
			const isMajor =
				Math.round(sec / tickInterval) % majorMultiple === 0

			ctx.beginPath()
			ctx.moveTo(x, isMajor ? 0 : 14)
			ctx.lineTo(x, RULER_HEIGHT)
			ctx.stroke()

			if (isMajor) {
				const min = Math.floor(sec / 60)
				const s = Math.floor(sec % 60)
				const label = `${min}:${s.toString().padStart(2, "0")}`
				const labelW = ctx.measureText(label).width
				const tx = Math.max(labelW / 2, x)
				ctx.fillText(label, tx, 10)
			}
		}
	}, [durationMs, pixelsPerSecond, totalWidth])

	/* ---- WaveSurfer ---- */
	useEffect(() => {
		if (!videoUrl || !waveformRef.current || !durationMs) return

		const durationSec = durationMs / 1000
		const colors = resolveWaveformColors()

		const ws = WaveSurfer.create({
			container: waveformRef.current,
			url: videoUrl,
			duration: durationSec,
			height: WAVEFORM_HEIGHT,
			waveColor: colors.wave,
			progressColor: colors.progress,
			cursorWidth: 0,
			barWidth: 2,
			barGap: 1,
			barRadius: 2,
			normalize: true,
			interact: false,
			minPxPerSec: pixelsPerSecond,
			hideScrollbar: true,
			fillParent: false,
			autoCenter: false,
			autoScroll: false,
			dragToSeek: false,
			mediaControls: false,
			backend: "MediaElement",
		})

		ws.setVolume(0)
		wsRef.current = ws

		return () => {
			ws.destroy()
			wsRef.current = null
		}
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [videoUrl, durationMs])

	useEffect(() => {
		const ws = wsRef.current
		if (!ws) return
		try {
			ws.zoom(pixelsPerSecond)
		} catch {
			// WaveSurfer might not be ready yet
		}
	}, [pixelsPerSecond])

	/* ---- Video frames extraction ---- */
	const framesCanvasRef = useRef<HTMLCanvasElement>(null)
	const framesCacheRef = useRef<{ timeSec: number; bitmap: ImageBitmap }[]>(
		[],
	)
	const [framesReady, setFramesReady] = useState(false)

	useEffect(() => {
		if (!videoUrl || !durationMs) return

		framesCacheRef.current.forEach((f) => f.bitmap.close())
		framesCacheRef.current = []
		setFramesReady(false)

		let cancelled = false

		const video = document.createElement("video")
		video.crossOrigin = "anonymous"
		video.muted = true
		video.preload = "auto"
		video.src = videoUrl

		const extract = async () => {
			await new Promise<void>((resolve, reject) => {
				video.onloadedmetadata = () => resolve()
				video.onerror = () => reject(new Error("video load error"))
				if (video.readyState >= 1) resolve()
			})

			if (cancelled) return

			const durationSec = durationMs / 1000
			const frameCount = Math.min(
				Math.ceil(durationSec / 2),
				MAX_EXTRACTED_FRAMES,
			)
			const interval = durationSec / frameCount
			const aspect = video.videoWidth / video.videoHeight || 16 / 9
			const frameW = Math.round(FRAMES_HEIGHT * aspect)

			const offCanvas = document.createElement("canvas")
			offCanvas.width = frameW * 2
			offCanvas.height = FRAMES_HEIGHT * 2
			const offCtx = offCanvas.getContext("2d")!

			const cache: { timeSec: number; bitmap: ImageBitmap }[] = []

			for (let i = 0; i < frameCount; i++) {
				if (cancelled) return

				const timeSec = i * interval
				video.currentTime = timeSec
				await new Promise<void>((r) => {
					video.onseeked = () => r()
				})

				if (cancelled) return

				offCtx.drawImage(
					video,
					0,
					0,
					offCanvas.width,
					offCanvas.height,
				)
				const bitmap = await createImageBitmap(offCanvas)
				cache.push({ timeSec, bitmap })
			}

			if (!cancelled) {
				framesCacheRef.current = cache
				setFramesReady(true)
			}
		}

		extract().catch(() => {})

		return () => {
			cancelled = true
			video.src = ""
			video.load()
		}
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [videoUrl, durationMs])

	const drawFrames = useCallback(() => {
		const container = timelineRef.current
		const canvas = framesCanvasRef.current
		if (!container || !canvas || !framesReady) return

		const cache = framesCacheRef.current
		if (cache.length === 0) return

		const sl = container.scrollLeft
		const vw = container.clientWidth
		if (!vw) return
		const canvasW = Math.min(vw + CANVAS_OVERSCAN * 2, totalWidth)
		const offset = Math.max(
			0,
			Math.min(sl - CANVAS_OVERSCAN, totalWidth - canvasW),
		)

		const dpr = window.devicePixelRatio || 1
		canvas.width = canvasW * dpr
		canvas.height = FRAMES_HEIGHT * dpr
		canvas.style.width = `${canvasW}px`
		canvas.style.height = `${FRAMES_HEIGHT}px`
		canvas.style.transform = `translateX(${offset}px)`

		const ctx = canvas.getContext("2d")
		if (!ctx) return
		ctx.scale(dpr, dpr)
		ctx.fillStyle = "#111"
		ctx.fillRect(0, 0, canvasW, FRAMES_HEIGHT)

		const firstBitmap = cache[0].bitmap
		const aspect = firstBitmap.width / firstBitmap.height || 16 / 9
		const naturalW = Math.round(FRAMES_HEIGHT * aspect)
		const step = Math.max(
			1,
			Math.ceil(cache.length / Math.max(1, Math.floor(totalWidth / naturalW))),
		)

		for (let i = 0; i < cache.length; i += step) {
			const globalX = cache[i].timeSec * pixelsPerSecond
			const nextIdx = Math.min(i + step, cache.length - 1)
			const nextGlobalX =
				nextIdx > i
					? cache[nextIdx].timeSec * pixelsPerSecond
					: totalWidth

			if (nextGlobalX < offset) continue
			if (globalX > offset + canvasW) break

			const x = globalX - offset
			const tileW = Math.max(naturalW, nextGlobalX - globalX)
			ctx.drawImage(cache[i].bitmap, x, 0, tileW, FRAMES_HEIGHT)
		}
	}, [framesReady, pixelsPerSecond, totalWidth])

	/* ---- Animation loop: playhead sync + canvas redraw ---- */
	const [playheadMs, setPlayheadMs] = useState(0)
	const animRef = useRef<number>(0)
	const lastScrollRef = useRef(-1)
	const lastViewportRef = useRef(-1)

	useEffect(() => {
		const tick = () => {
			if (playerRef.current) {
				const timeMs = playerRef.current.currentTime * 1000
				setPlayheadMs(timeMs)

				const ws = wsRef.current
				if (ws) {
					try {
						ws.setTime(playerRef.current.currentTime)
					} catch {
						// ignore
					}
				}
			}

			const container = timelineRef.current
			if (container) {
				const sl = container.scrollLeft
				const vw = container.clientWidth
				if (
					sl !== lastScrollRef.current ||
					vw !== lastViewportRef.current
				) {
					lastScrollRef.current = sl
					lastViewportRef.current = vw
					drawRuler()
					drawFrames()
				}
			}

			animRef.current = requestAnimationFrame(tick)
		}
		animRef.current = requestAnimationFrame(tick)

		return () => {
			if (animRef.current) cancelAnimationFrame(animRef.current)
			lastScrollRef.current = -1
			lastViewportRef.current = -1
		}
	}, [drawRuler, drawFrames])

	const [isApplying, setIsApplying] = useState(false)

	const handleApply = async () => {
		if (cutRegions.length === 0) {
			setIsApplying(true)
			try {
				await skipSilenceApply()
			} finally {
				setIsApplying(false)
			}
			return
		}

		setIsApplying(true)
		try {
			await startSilenceApply(
				cutRegions.map((r) => ({
					start_ms: Math.round(r.startMs),
					end_ms: Math.round(r.endMs),
				})),
			)
		} finally {
			setIsApplying(false)
		}
	}

	return (
		<div
			className={cs(styles.root, className)}
			data-testid="FragmentsStep"
		>
			{/* Video player */}
			<div className={styles.playerWrapper}>
				{videoUrl && (
					<MediaPlayer
						ref={playerRef}
						src={videoUrl}
						crossOrigin=""
						playsInline
					>
						<MediaProvider />
						<DefaultVideoLayout icons={defaultLayoutIcons} />
					</MediaPlayer>
				)}
			</div>

			{/* Timeline section */}
			<div className={styles.timelineSection}>
				<div className={styles.zoomControls}>
					<button
						className={styles.zoomButton}
						onClick={() =>
							setPixelsPerSecond((p) =>
								Math.max(MIN_PPS, p - PPS_STEP),
							)
						}
					>
						-
					</button>
					<span>Масштаб</span>
					<button
						className={styles.zoomButton}
						onClick={() =>
							setPixelsPerSecond((p) =>
								Math.min(MAX_PPS, p + PPS_STEP),
							)
						}
					>
						+
					</button>
				</div>

				<div
					ref={timelineRef}
					className={styles.timelineContainer}
					onClick={handleTimelineClick}
					onContextMenu={(e) => handleContextMenu(e, null)}
				>
					<div
						className={styles.timelineInner}
						style={{ width: `${totalWidth}px` }}
					>
						<div className={styles.rulerRow}>
							<canvas
								ref={rulerRef}
								className={styles.rulerCanvas}
							/>
						</div>

						<div className={styles.framesRow}>
							<canvas
								ref={framesCanvasRef}
								className={styles.framesCanvas}
							/>
						</div>

						<div className={styles.waveformRow}>
							<div
								ref={waveformRef}
								style={{ height: WAVEFORM_HEIGHT }}
							/>
						</div>

						<div className={styles.cutRegionsRow}>
							{cutRegions.map((region, index) => {
								const left = msToPixels(region.startMs)
								const width = msToPixels(
									region.endMs - region.startMs,
								)

								return (
									<div
										key={region.id}
										data-testid="cut-region"
										className={styles.cutRegion}
										style={{
											left: `${left}px`,
											width: `${width}px`,
										}}
										onPointerDown={(e) => {
											if (e.button === 0) {
												handleRegionDragStart(e, index)
											}
										}}
										onContextMenu={(e) =>
											handleContextMenu(e, region.id)
										}
									>
										<div
											className={styles.handleLeft}
											onPointerDown={(e) =>
												handleResizePointerDown(
													e,
													index,
													"left",
												)
											}
										/>
										<div
											className={styles.handleRight}
											onPointerDown={(e) =>
												handleResizePointerDown(
													e,
													index,
													"right",
												)
											}
										/>
									</div>
								)
							})}

							<div
								className={styles.playhead}
								style={{
									left: `${msToPixels(playheadMs)}px`,
								}}
							/>
						</div>
					</div>
				</div>
			</div>

			{/* Info bar */}
			<div className={styles.infoBar}>
				<span>Фрагментов: {cutRegions.length}</span>
				<span className={styles.infoTotal}>
					Будет удалено: {formatDuration(totalRemovedMs)}
				</span>
			</div>

			{/* Context menu */}
			{contextMenu && (
				<div
					className={styles.contextMenu}
					style={{
						position: "fixed",
						left: contextMenu.x,
						top: contextMenu.y,
						zIndex: 9999,
					}}
					onClick={(e) => e.stopPropagation()}
				>
					{contextMenu.regionId && (
						<button
							className={cs(
								styles.contextMenuItem,
								styles.contextMenuDanger,
							)}
							onClick={() => {
								removeRegion(contextMenu.regionId!)
								setContextMenu(null)
							}}
						>
							<Trash2 size={14} />
							<span>Удалить</span>
						</button>
					)}
					<button
						className={styles.contextMenuItem}
						onClick={() => {
							addRegion(contextMenu.timeMs)
							setContextMenu(null)
						}}
					>
						<Plus size={14} />
						<span>Добавить новый</span>
					</button>
				</div>
			)}

			{/* Footer */}
			<div className={styles.footer}>
				<Button
					variant="outline"
					disabled={isApplying}
					onClick={goBack}
				>
					Отмена
				</Button>
				<Button
					variant="primary"
					disabled={isApplying}
					onClick={handleApply}
				>
					{cutRegions.length === 0 ? "Пропустить" : "Применить"}
				</Button>
			</div>
		</div>
	)
}
