"use client"

import type { ISubtitlesTrackProps } from "./SubtitlesTrack.d"
import type { JSX } from "react"

import { useQueryClient } from "@tanstack/react-query"
import { useMediaState } from "@vidstack/react"
import cs from "classnames"
import {
	FunctionComponent,
	memo,
	useCallback,
	useMemo,
	useRef,
	useState,
} from "react"
import { createPortal } from "react-dom"

import api from "@shared/api"
import { fetchClient } from "@shared/api"
import { useSegmentResize } from "@shared/hooks/useSegmentResize"
import { SegmentEditModal } from "@features/project/SegmentEditModal"

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

import {
	type WordData,
	estimateWordTimings,
} from "@shared/lib/transcriptionDocument"

interface Segment {
	start: number
	end: number
	text: string
	words?: WordData[]
}

const MIN_SEGMENT_DURATION = 0.1
const VIRTUAL_OVERSCAN_PX = 600

/* ------------------------------------------------------------------ */
/*  Individual segment — memoized for performance                      */
/* ------------------------------------------------------------------ */

const SegmentBlock = memo(
	({
		segment,
		index,
		pixelsPerSecond,
		isActive,
		isResizing,
		onClick,
		onHandlePointerDown,
		onMouseEnter,
		onMouseLeave,
	}: {
		segment: Segment
		index: number
		pixelsPerSecond: number
		isActive: boolean
		isResizing: boolean
		onClick: (index: number) => void
		onHandlePointerDown: (
			e: React.PointerEvent,
			index: number,
			edge: "left" | "right",
		) => void
		onMouseEnter: (e: React.MouseEvent, text: string) => void
		onMouseLeave: () => void
	}) => {
		const left = segment.start * pixelsPerSecond
		const width = Math.max(1, (segment.end - segment.start) * pixelsPerSecond)

		return (
			<div
				className={cs(styles.segment, {
					[styles.active]: isActive,
					[styles.resizing]: isResizing,
				})}
				style={{ left, width }}
				onClick={(e) => {
					e.stopPropagation()
					onClick(index)
				}}
				onMouseEnter={(e) => onMouseEnter(e, segment.text)}
				onMouseLeave={onMouseLeave}
			>
				<div
					className={styles.handleLeft}
					onPointerDown={(e) => onHandlePointerDown(e, index, "left")}
				/>
				{width > 20 && (
					<span className={styles.segmentText}>{segment.text}</span>
				)}
				<div
					className={styles.handleRight}
					onPointerDown={(e) => onHandlePointerDown(e, index, "right")}
				/>
			</div>
		)
	},
)

SegmentBlock.displayName = "SegmentBlock"

/* ------------------------------------------------------------------ */
/*  Main component                                                     */
/* ------------------------------------------------------------------ */

export const SubtitlesTrack: FunctionComponent<
	ISubtitlesTrackProps
> = ({
	artifactId,
	pixelsPerSecond,
	height,
	duration,
	scrollLeft = 0,
	viewportWidth = 2000,
	videoUrl,
}): JSX.Element => {
	const currentTime = useMediaState("currentTime")
	const queryClient = useQueryClient()

	const [editModalOpen, setEditModalOpen] = useState(false)
	const [editingIndex, setEditingIndex] = useState(-1)

	const { data: transcription } = api.useQuery(
		"get",
		"/api/transcribe/transcriptions/by-artifact/{artifact_id}/",
		{ params: { path: { artifact_id: artifactId } } },
		{ enabled: !!artifactId },
	)

	/* Parse segments from transcription document */
	const [localSegments, setLocalSegments] = useState<Segment[] | null>(null)

	const apiSegments: Segment[] = useMemo(() => {
		if (!transcription?.document) return []
		const doc = transcription.document as {
			segments?: Array<{
				time?: { start?: number; end?: number }
				text?: string
				lines?: Array<{
					words?: Array<{
						text?: string
						time?: { start?: number; end?: number }
					}>
				}>
			}>
		}
		return (
			doc.segments?.map((seg) => {
				const words: WordData[] = []
				if (seg.lines) {
					for (const line of seg.lines) {
						if (line.words) {
							for (const w of line.words) {
								if (w.text) {
									words.push({
										text: w.text,
										start: w.time?.start ?? 0,
										end: w.time?.end ?? 0,
									})
								}
							}
						}
					}
				}
				const segStart = seg.time?.start ?? 0
				const segEnd = seg.time?.end ?? 0
				const segText = seg.text ?? ""
				return {
					start: segStart,
					end: segEnd,
					text: segText,
					words:
						words.length > 0
							? words
							: estimateWordTimings(segText, segStart, segEnd),
				}
			}) ?? []
		)
	}, [transcription])

	const segments = localSegments ?? apiSegments

	/* Reset local overrides when API data changes */
	const prevApiRef = useRef(apiSegments)
	if (prevApiRef.current !== apiSegments) {
		prevApiRef.current = apiSegments
		if (localSegments !== null) setLocalSegments(null)
	}

	/* ---- Save helpers ---- */

	const buildDocument = useCallback((segs: Segment[]) => {
		return {
			segments: segs.map((seg) => {
				const wordNodes = seg.words
					? seg.words.map((w) => ({
							text: w.text,
							semantic_tags: [],
							structure_tags: [],
							time: { start: w.start, end: w.end },
						}))
					: []
				return {
					text: seg.text,
					semantic_tags: [],
					structure_tags: [],
					time: { start: seg.start, end: seg.end },
					lines: [
						{
							text: seg.text,
							semantic_tags: [],
							structure_tags: [],
							time: { start: seg.start, end: seg.end },
							words: wordNodes,
						},
					],
				}
			}),
		}
	}, [])

	const invalidateTranscription = useCallback(() => {
		queryClient.invalidateQueries({
			queryKey: api.queryOptions(
				"get",
				"/api/transcribe/transcriptions/by-artifact/{artifact_id}/",
				{ params: { path: { artifact_id: artifactId } } },
			).queryKey,
		})
	}, [queryClient, artifactId])

	const patchTranscription = useCallback(
		async (segs: Segment[]) => {
			if (!transcription?.id) return
			try {
				await fetchClient.PATCH(
					"/api/transcribe/transcriptions/{transcription_id}/",
					{
						params: {
							path: { transcription_id: transcription.id },
						},
						body: { document: buildDocument(segs) },
					},
				)
				invalidateTranscription()
			} catch {
				// Revert to API data on failure
				setLocalSegments(null)
			}
		},
		[transcription, buildDocument, invalidateTranscription],
	)

	/* ---- Resize logic ---- */

	const onResize = useCallback(
		(index: number, edge: "left" | "right", deltaSec: number) => {
			setLocalSegments((prev) => {
				const segs = [...(prev ?? apiSegments)]
				const seg = { ...segs[index] }
				const prevSeg = index > 0 ? segs[index - 1] : null
				const nextSeg = index < segs.length - 1 ? segs[index + 1] : null

				if (edge === "left") {
					let newStart = seg.start + deltaSec
					const minStart = prevSeg ? prevSeg.end : 0
					newStart = Math.max(newStart, minStart)
					newStart = Math.min(newStart, seg.end - MIN_SEGMENT_DURATION)
					seg.start = Math.round(newStart * 1000) / 1000
				} else {
					let newEnd = seg.end + deltaSec
					const maxEnd = nextSeg ? nextSeg.start : duration
					newEnd = Math.min(newEnd, maxEnd)
					newEnd = Math.max(newEnd, seg.start + MIN_SEGMENT_DURATION)
					seg.end = Math.round(newEnd * 1000) / 1000
				}

				segs[index] = seg
				return segs
			})
		},
		[apiSegments, duration],
	)

	const localSegmentsRef = useRef(localSegments)
	localSegmentsRef.current = localSegments

	const onResizeEnd = useCallback(
		() => {
			const segs = localSegmentsRef.current ?? apiSegments
			patchTranscription(segs)
		},
		[apiSegments, patchTranscription],
	)

	const { handlePointerDown, resizingIndex, justResizedRef } =
		useSegmentResize({
			pixelsPerSecond,
			onResize,
			onResizeEnd,
		})

	/* ---- Tooltip ---- */

	const [tooltip, setTooltip] = useState<{
		text: string
		x: number
		y: number
	} | null>(null)

	const handleSegmentMouseEnter = useCallback(
		(e: React.MouseEvent, text: string) => {
			const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
			setTooltip({ text, x: rect.left, y: rect.top - 4 })
		},
		[],
	)

	const handleSegmentMouseLeave = useCallback(() => {
		setTooltip(null)
	}, [])

	/* ---- Click to open modal ---- */

	const handleSegmentClick = useCallback(
		(index: number) => {
			if (justResizedRef.current) return

			setEditingIndex(index)
			setEditModalOpen(true)
		},
		[justResizedRef],
	)

	const handleModalSave = useCallback(
		async (newText: string) => {
			const segs = [...(localSegments ?? apiSegments)]
			if (editingIndex < 0 || editingIndex >= segs.length) return
			segs[editingIndex] = { ...segs[editingIndex], text: newText }
			setLocalSegments(segs)
			await patchTranscription(segs)
		},
		[localSegments, apiSegments, editingIndex, patchTranscription],
	)

	const handleModalSplit = useCallback(
		async (
			newSegments: Array<{
				start: number
				end: number
				text: string
				words?: WordData[]
			}>,
		) => {
			const segs = [...(localSegments ?? apiSegments)]
			if (editingIndex < 0 || editingIndex >= segs.length) return
			segs.splice(editingIndex, 1, ...newSegments)
			setLocalSegments(segs)
			await patchTranscription(segs)
		},
		[localSegments, apiSegments, editingIndex, patchTranscription],
	)

	/* ---- Virtual windowing ---- */

	const totalWidth = Math.max(duration * pixelsPerSecond, 200)
	const overscan = Math.max(VIRTUAL_OVERSCAN_PX, viewportWidth * 0.5)
	const visibleStartTime = Math.max(0, (scrollLeft - overscan) / pixelsPerSecond)
	const visibleEndTime = (scrollLeft + viewportWidth + overscan) / pixelsPerSecond

	const visibleSegments = useMemo(() => {
		const result: Array<{ segment: Segment; index: number }> = []
		for (let i = 0; i < segments.length; i++) {
			const seg = segments[i]
			if (seg.end >= visibleStartTime && seg.start <= visibleEndTime) {
				result.push({ segment: seg, index: i })
			}
		}
		return result
	}, [segments, visibleStartTime, visibleEndTime])

	const activeIndex = useMemo(
		() => segments.findIndex((s) => currentTime >= s.start && currentTime <= s.end),
		[segments, currentTime],
	)

	const editingSegment = editingIndex >= 0 && editingIndex < segments.length
		? segments[editingIndex]
		: { start: 0, end: 0, text: "" }

	return (
		<>
			<div
				className={styles.wrapper}
				style={{ width: totalWidth, height }}
				data-testid="SubtitlesTrack"
			>
				{visibleSegments.map(({ segment, index }) => (
					<SegmentBlock
						key={index}
						segment={segment}
						index={index}
						pixelsPerSecond={pixelsPerSecond}
						isActive={index === activeIndex}
						isResizing={index === resizingIndex}
						onClick={handleSegmentClick}
						onHandlePointerDown={handlePointerDown}
						onMouseEnter={handleSegmentMouseEnter}
						onMouseLeave={handleSegmentMouseLeave}
					/>
				))}
			</div>

			<SegmentEditModal
				open={editModalOpen}
				onOpenChange={setEditModalOpen}
				videoUrl={videoUrl}
				segment={editingSegment}
				onSave={handleModalSave}
				onSplit={handleModalSplit}
			/>

			{tooltip &&
				createPortal(
					<div
						className={styles.tooltip}
						style={{
							left: tooltip.x,
							top: tooltip.y,
							transform: "translateY(-100%)",
						}}
					>
						{tooltip.text}
					</div>,
					document.body,
				)}
		</>
	)
}
