package com.nate.posedetection

import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chaintech.videoplayer.host.MediaPlayerHost
import chaintech.videoplayer.model.ScreenResize
import chaintech.videoplayer.model.VideoPlayerConfig
import chaintech.videoplayer.ui.video.VideoPlayerComposable
import chaintech.videoplayer.util.RetrieveMediaDuration
import com.nate.posedetection.theme.AppTheme
import com.performancecoachlab.posedetection.camera.CameraView
import com.performancecoachlab.posedetection.camera.CameraViewControllerImpl
import com.performancecoachlab.posedetection.camera.DetectMode
import com.performancecoachlab.posedetection.camera.DrawableObject
import com.performancecoachlab.posedetection.camera.DrawableShape
import com.performancecoachlab.posedetection.camera.PreviewFillMode
import com.performancecoachlab.posedetection.custom.CustomObjectRespository
import com.performancecoachlab.posedetection.custom.ModelPath
import com.performancecoachlab.posedetection.custom.rememberObjectModel
import com.performancecoachlab.posedetection.encoding.VideoBuilder
import com.performancecoachlab.posedetection.encoding.createVideoBuilder
import com.performancecoachlab.posedetection.permissions.PermissionProvider
import com.performancecoachlab.posedetection.recording.AnalysisObject
import com.performancecoachlab.posedetection.recording.FrameAnalyser
import com.performancecoachlab.posedetection.recording.InputFrame
import com.performancecoachlab.posedetection.recording.AnalysisResult
import com.performancecoachlab.posedetection.recording.FrameSize
import com.performancecoachlab.posedetection.recording.extractFrame
import com.performancecoachlab.posedetection.recording.extractFrames
import com.performancecoachlab.posedetection.recording.listVideoFrameTimestamps
import com.performancecoachlab.posedetection.skeleton.Pose
import com.performancecoachlab.posedetection.skeleton.Skeleton
import com.performancecoachlab.posedetection.skeleton.SkeletonRepository
import io.github.vinceglb.filekit.FileKit
import io.github.vinceglb.filekit.PlatformFile
import io.github.vinceglb.filekit.absolutePath
import io.github.vinceglb.filekit.dialogs.FileKitType
import io.github.vinceglb.filekit.dialogs.compose.rememberShareFileLauncher
import io.github.vinceglb.filekit.dialogs.openFilePicker
import io.github.vinceglb.filekit.extension
import io.github.vinceglb.filekit.filesDir
import io.github.vinceglb.filekit.path
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.roundToLong
import kotlin.time.Clock
import kotlin.time.ExperimentalTime


@Composable
internal fun App() = AppTheme {
    var selectedTabIndex by remember { mutableStateOf(0) }
    val tabs = listOf("Camera Feed", "Recorded Video")

    // Apply safe-area insets once to the whole screen.
    Column(
        modifier = Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.safeDrawing)
    ) {
        // Tab row should be only as tall as needed.
        TabRow(selectedTabIndex = selectedTabIndex, modifier = Modifier.fillMaxWidth()) {
            tabs.forEachIndexed { index, title ->
                Tab(
                    selected = selectedTabIndex == index,
                    onClick = { selectedTabIndex = index },
                    text = { Text(title) })
            }
        }

        // Content always gets the remaining space.
        Box(
            modifier = Modifier.weight(1f).fillMaxWidth()
        ) {
            when (selectedTabIndex) {
                0 -> CameraSample()
                1 -> RecordedSample()
            }
        }
    }
}

@Composable
fun RecordedSample() {
    val availableModels = discoverModels()
    var selectedModel by remember(availableModels) { mutableStateOf(availableModels.firstOrNull()) }
    var videoFile: PlatformFile? = null
    var path by remember { mutableStateOf("") }
    var extension by remember { mutableStateOf("") }
    val scope = rememberCoroutineScope()
    var loading by remember { mutableStateOf(false) }
    var totalTime by remember { mutableStateOf<Long?>(null) }

    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(8.dp),
        ) {
            Button(onClick = {
                scope.launch {
                    videoFile = FileKit.openFilePicker(type = FileKitType.Video)
                    loading = true
                    videoFile?.also { vid ->
                        path = vid.absolutePath()
                        extension = vid.extension
                    }
                    loading = false
                }
            }) {
                Text("Select Video")
            }

            // Model picker
            var modelMenuExpanded by remember { mutableStateOf(false) }
            Box {
                Button(onClick = { modelMenuExpanded = true }) {
                    Text(selectedModel?.name ?: "No model", fontSize = 12.sp)
                }
                DropdownMenu(
                    expanded = modelMenuExpanded,
                    onDismissRequest = { modelMenuExpanded = false }
                ) {
                    if (availableModels.isEmpty()) {
                        DropdownMenuItem(text = { Text("No models found") }, onClick = {})
                    } else {
                        availableModels.forEach { model ->
                            DropdownMenuItem(
                                text = {
                                    Row(
                                        verticalAlignment = Alignment.CenterVertically,
                                        horizontalArrangement = Arrangement.spacedBy(8.dp)
                                    ) {
                                        RadioButton(
                                            selected = selectedModel == model,
                                            onClick = null
                                        )
                                        Text(model.name)
                                    }
                                },
                                onClick = {
                                    selectedModel = model
                                    modelMenuExpanded = false
                                }
                            )
                        }
                    }
                }
            }
        }
        AnimatedContent(path) {
            if (it.isNotBlank()) {
                RetrieveMediaDuration(
                    url = path, onDurationRetrieved = { duration ->
                        totalTime = (1000 * duration).roundToLong()
                    })
                totalTime?.also { duration ->
                    FrameAnalysis(
                        url = path,
                        modifier = Modifier.weight(1f),
                        duration = duration,
                        selectedModel = selectedModel,
                    )
                }

            } else if (loading) {
                Text("Extracting Video.")
            }
        }
    }
}

@Composable
fun FrameAnalysis(
    url: String, modifier: Modifier = Modifier, duration: Long, selectedModel: NamedModel?
) {
    val coroutineScope = rememberCoroutineScope()
    var currentJob by remember { mutableStateOf<Job?>(null) }
    var image by remember { mutableStateOf<InputFrame?>(null) }
    val timeRange = Pair(0L, duration)
    var frame by remember { mutableStateOf(timeRange.first) }
    val modelPath = selectedModel?.path ?: ModelPath()
    val generalModel = rememberObjectModel(modelPath)
    val frameAnalyser by remember(modelPath) { mutableStateOf(FrameAnalyser(generalModel)) }
    var bitmap by remember { mutableStateOf<ImageBitmap?>(null) }
    val videoBuilder = remember { mutableStateOf<VideoBuilder?>(null) }
    var isRecording by remember { mutableStateOf(false) }
    val launcher = rememberShareFileLauncher()
    val file = PlatformFile(FileKit.filesDir, "video.mp4")
    var savedPath: String? by remember { mutableStateOf(null) }
    var buildTimeMs: Long? by remember { mutableStateOf(null) }
    var buildStartMs: Long? by remember { mutableStateOf(null) }
    lateinit var size: Pair<Int, Int>
    var firstFrameTimestamp: Long? by remember { mutableStateOf(null) }

    // Function to start recording
    fun startRecording() {
        isRecording = true
        buildStartMs = Clock.System.now().toEpochMilliseconds()
        coroutineScope.launch {
            try {
                val videoFile = file.path

                // Create the video builder with 30fps
                videoBuilder.value = createVideoBuilder(
                    outputPath = videoFile, fps = 30, width = size.first, height = size.second
                )
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    // Function to stop recording and finalize video
    fun stopRecording() {
        coroutineScope.launch {
            try {
                videoBuilder.value?.let { builder ->
                    savedPath = builder.finalize()
                    buildTimeMs = buildStartMs?.let {
                        Clock.System.now().toEpochMilliseconds() - it
                    }
                    videoBuilder.value = null
                    firstFrameTimestamp = null
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    val frameMap: MutableMap<String, List<Long>> = mutableMapOf()

    LaunchedEffect(url) {
        val timestamps = listVideoFrameTimestamps(url)
        frameMap[url] = timestamps
    }

    LaunchedEffect(url, frame) {
        currentJob?.cancel()
        currentJob = coroutineScope.launch {
            try {
                val extractedFrame = extractFrame(url, frame)
                extractedFrame?.let { frame ->
                    frameAnalyser.analyseFrame(frame).let { analysisResults ->
                        frame.drawAnalysisResults(analysisResults).also {
                            if (!isRecording) {
                                size = Pair(it.width, it.height)
                                startRecording()
                            }
                            if (firstFrameTimestamp == null) firstFrameTimestamp = frame.timestamp
                            val relativeTimestamp =
                                frame.timestamp - (firstFrameTimestamp ?: frame.timestamp)
                            videoBuilder.value?.addFrame(it, relativeTimestamp)
                            bitmap = it
                        }

                    }
                }
            } catch (e: Exception) {
                // Handle any error that may occur
                e.printStackTrace()
            } finally {
                val nextFrame = frameMap[url]?.let { frameTimeStamps ->
                    frameTimeStamps.firstOrNull { it > frame }
                } ?: (
                        // Fallback:
                        // If no timestamp is found (e.g., end of list or metadata missing),
                        // assume a 50 FPS video and increment by 20ms.
                        // This keeps playback and processing moving forward smoothly.
                        frame + 20L)
                nextFrame.also {
                    if (it < timeRange.second) {
                        frame = it
                    } else {
                        stopRecording()
                    }
                }
            }
        }
    }

    DisposableEffect(url) {
        onDispose {
            currentJob?.cancel()
        }
    }

    Box(modifier = modifier.fillMaxSize()) {
        buildTimeMs?.let { ms ->
            val seconds = ms / 1000.0
            // Format to one decimal place in commonMain (multiplatform) without
            // relying on platform-specific String.format.
            val display = (seconds * 10).roundToLong() / 10.0
            androidx.compose.material3.Text(
                text = "Build time: ${display}s",
                modifier = Modifier.align(androidx.compose.ui.Alignment.TopCenter)
                    .padding(top = 8.dp),
                style = androidx.compose.material3.MaterialTheme.typography.titleMedium,
            )
            // Ensure the lambda returns Unit explicitly so Kotlin can infer the
            // type parameter R of `let` in this composable context.
            Unit
        }
        if (savedPath != null) {
            val playerHost = remember {
                MediaPlayerHost(
                    mediaUrl = savedPath ?: "",
                    isLooping = true,
                    isPaused = false,
                    isMuted = true,
                    initialVideoFitMode = ScreenResize.FIT,
                )
            }
            VideoPlayerComposable(
                modifier = Modifier.padding(16.dp).fillMaxSize(),
                playerHost = playerHost,
                playerConfig = VideoPlayerConfig(
                    isSeekBarVisible = true,
                    isDurationVisible = true,
                    isFastForwardBackwardEnabled = false,
                    isMuteControlEnabled = false,
                    isSpeedControlEnabled = false,
                    isScreenLockEnabled = false,
                    isScreenResizeEnabled = false,
                    isFullScreenEnabled = false,
                    isPauseResumeEnabled = true,
                )
            )
        } else if (bitmap != null) {
            bitmap?.also {
                Image(
                    bitmap = it,
                    contentDescription = "Static Thumbnail",
                    contentScale = ContentScale.Fit,
                    modifier = Modifier.fillMaxSize()
                )
            }
        } else {
            CircularProgressIndicator(
                modifier = Modifier.align(Alignment.Center),
            )
        }
    }
}

// DebugTestScreen removed.
// CameraSample starts below.
private object DebugTestScreenRemoved

@Suppress("unused")
@Composable
private fun debugTestScreenRemoved() {
    val availableModels = discoverModels()
    var selectedModel by remember(availableModels) { mutableStateOf(availableModels.firstOrNull()) }
    val modelPath = selectedModel?.path ?: ModelPath()
    val generalModel = rememberObjectModel(modelPath)

    val skeletonRepository = remember { SkeletonRepository() }
    val customObjectRepository = remember { CustomObjectRespository() }
    val skeleton by skeletonRepository.skeletonFlow.collectAsState()
    val customObjects by customObjectRepository.customObjectFlow.collectAsState()
    var permissionGranted by remember { mutableStateOf(false) }
    var detectMode by remember { mutableStateOf(DetectMode.BOTH) }

    // Test phases: WAITING_PERMISSION -> PREVIEW -> RECORDING -> COMPARING -> DONE
    var phase by remember { mutableStateOf("WAITING_PERMISSION") }
    var recordingId: String? by remember { mutableStateOf(null) }
    var originalVideoPath by remember { mutableStateOf("") }
    var processedVideoPath: String? by remember { mutableStateOf(null) }
    // Comparison test data
    data class ComparisonResult(
        val image: ImageBitmap,
        val label: String,
        val frameTs: Long,          // video PTS of this frame
        val frameElapsedMs: Long,   // elapsed from video start
        val expectedElapsedMs: Long, // where the skeleton was expected to match
        val liveSkeleton: Skeleton?,
        val videoPath: String,
        val allTimestamps: List<Long>,
        val angleMatch: Float,
        val posMatch: Float
    )
    var comparisonImages by remember { mutableStateOf<List<ComparisonResult>>(emptyList()) }
    var findBestResult by remember { mutableStateOf<Pair<Int, List<Pair<ImageBitmap, String>>>?>(null) }
    var statusText by remember { mutableStateOf("Waiting for camera permission...") }


    // Timing
    var recordTimeMs by remember { mutableStateOf(0L) }
    var extractTimeMs by remember { mutableStateOf(0L) }
    var frameCount by remember { mutableStateOf(0) }

    // Controller to get feed dimensions from CameraView.
    val controller = remember { CameraViewControllerImpl() }
    var feedWidth by remember { mutableStateOf(0f) }
    var feedHeight by remember { mutableStateOf(0f) }
    // Actual CameraView display size in pixels (for cropOffsetY calculation)
    var cameraViewPxW by remember { mutableStateOf(0f) }
    var cameraViewPxH by remember { mutableStateOf(0f) }

    // Saved analysis results during recording, keyed by timestamp ms.
    val analysisResults = remember { mutableMapOf<Long, AnalysisResult>() }
    // Wall-clock time when recording starts.
    var videoStartWallClock by remember { mutableStateOf(0L) }
    var recordingActive by remember { mutableStateOf(false) }

    // Deferred to wait for video save callback
    val videoSavedDeferred = remember { mutableStateOf<CompletableDeferred<String>?>(null) }

    val coroutineScope = rememberCoroutineScope()

    PermissionProvider().apply {
        if (!hasCameraPermission()) RequestCameraPermission(onGranted = {
            permissionGranted = true
        }, onDenied = { permissionGranted = false }) else permissionGranted = true
    }

    // Save skeleton + objects together. Objects don't have their own timestamp,
    // so we cache the latest objects and attach them when a skeleton arrives.
    var latestObjects by remember { mutableStateOf<List<AnalysisObject>>(emptyList()) }
    LaunchedEffect(Unit) {
        customObjectRepository.customObjectFlow.collect { objs ->
            if (objs != null) latestObjects = objs
        }
    }
    LaunchedEffect(Unit) {
        skeletonRepository.skeletonFlow.collect { skel ->
            // Only save skeletons after recording has started (videoStartWallClock set by onRecordToggled)
            if (recordingActive && videoStartWallClock > 0 && skel != null) {
                analysisResults[skel.timestamp] = AnalysisResult(skel, latestObjects)
            }
        }
    }

    // Go to preview once permission is granted
    LaunchedEffect(permissionGranted) {
        if (permissionGranted && phase == "WAITING_PERMISSION") {
            phase = "PREVIEW"
            statusText = "Tap Record to start"
        }
    }

    // Recording trigger — launched when phase becomes RECORDING
    LaunchedEffect(phase) {
        if (phase == "RECORDING") {
            statusText = "Recording 3 seconds..."
            analysisResults.clear()
            val deferred = CompletableDeferred<String>()
            videoSavedDeferred.value = deferred
            val recordStart = Clock.System.now().toEpochMilliseconds()
            videoStartWallClock = 0L
            recordingActive = false
            val beforeSetId = System.currentTimeMillis()
            println("RECORD-TIMING: before setRecordingId wallMs=$beforeSetId")
            recordingId = "${Clock.System.now().epochSeconds}"
            val afterSetId = System.currentTimeMillis()
            println("RECORD-TIMING: after setRecordingId wallMs=$afterSetId delta=${afterSetId - beforeSetId}ms")

            delay(1500)

            recordingId = null
            statusText = "Waiting for video to save..."
            val savedPath = deferred.await()
            recordTimeMs = Clock.System.now().toEpochMilliseconds() - recordStart

            val goodSkeletons = analysisResults.values.count { it.skeleton != null && it.skeleton!!.joints().size >= 10 }
            println("DEBUG: skeleton data: $goodSkeletons good skeletons")

            originalVideoPath = savedPath
            phase = "COMPARING"
            statusText = "Analysing timestamp relationship..."

            val capturedAnalysisResults = analysisResults.toMap()
            val capturedVideoStart = videoStartWallClock

            val extractJob = coroutineScope.launch(kotlinx.coroutines.Dispatchers.Default) {
                val timestamps = listVideoFrameTimestamps(savedPath)
                val savedKeys = capturedAnalysisResults.keys.sorted()
                val firstTs = timestamps.firstOrNull() ?: return@launch
                println("DEBUG: ${timestamps.size} video frames, ${savedKeys.size} analysis results")
                println("DEBUG: videoStartWallClock=$capturedVideoStart")
                println("DEBUG: video PTS range: ${timestamps.first()}..${timestamps.last()} span=${timestamps.last() - timestamps.first()}ms")
                println("DEBUG: skeleton ts range: ${savedKeys.firstOrNull()}..${savedKeys.lastOrNull()} span=${if (savedKeys.size > 1) savedKeys.last() - savedKeys.first() else 0}ms")

                // Diagnostic: compare video PTS against skeleton timestamps
                // Video PTS is relative (starts from ~0). Skeleton timestamps are wall-clock.
                // If we subtract videoStartWallClock from skeleton timestamps, they should
                // become comparable to video PTS. The difference reveals the lag.
                val relativeKeys = savedKeys.map { it - capturedVideoStart }
                println("DIAG: relative skeleton timestamps (first 10): ${relativeKeys.take(10)}")
                println("DIAG: video PTS (first 10): ${timestamps.take(10)}")

                // For each skeleton, find the closest video PTS and report the difference
                val offsets = relativeKeys.mapNotNull { skelRelTs ->
                    val closestPts = timestamps.minByOrNull { kotlin.math.abs(it - skelRelTs) } ?: return@mapNotNull null
                    skelRelTs - closestPts
                }
                if (offsets.isNotEmpty()) {
                    println("DIAG: skeleton-to-video offsets (first 10): ${offsets.take(10)}")
                    println("DIAG: avg offset=${offsets.average().toLong()}ms min=${offsets.min()}ms max=${offsets.max()}ms")
                }

                // Pick a frame near 1.5s that has BOTH skeleton (10+ joints) AND objects
                val targetTime = capturedVideoStart + 750L
                val bestKey = savedKeys
                    .filter { key ->
                        val r = capturedAnalysisResults[key]
                        r?.skeleton != null && r.skeleton!!.joints().size >= 10 && r.objects.isNotEmpty()
                    }
                    .minByOrNull { kotlin.math.abs(it - targetTime) }
                    ?: savedKeys  // fallback: just skeleton if no frame has both
                        .filter { capturedAnalysisResults[it]?.skeleton?.joints()?.size ?: 0 >= 10 }
                        .minByOrNull { kotlin.math.abs(it - targetTime) }
                val liveResult = bestKey?.let { capturedAnalysisResults[it] }
                val liveSkel = liveResult?.skeleton
                val liveObjects = liveResult?.objects ?: emptyList()
                if (liveSkel == null || liveSkel.joints().size < 10) {
                    println("DEBUG: no good skeleton found")
                    phase = "DONE"
                    statusText = "No skeleton with objects found"
                    return@launch
                }
                println("DEBUG: selected frame has ${liveSkel.joints().size} joints, ${liveObjects.size} objects")
                val skelVideoTs = bestKey - capturedVideoStart
                // Log all skeleton timestamps near the target for debugging
                val nearKeys = savedKeys.filter { kotlin.math.abs(it - targetTime) < 500 }
                println("DEBUG: skeleton at sensorTs=$bestKey videoTs=${skelVideoTs}ms joints=${liveSkel.joints().size}")
                println("DEBUG: videoStartWallClock=$capturedVideoStart targetTime=$targetTime")
                println("DEBUG: nearby skeleton keys (within 500ms of target): ${nearKeys.map { it - capturedVideoStart }}")
                println("DEBUG: first 5 skeleton keys (relative): ${savedKeys.take(5).map { it - capturedVideoStart }}")
                println("DEBUG: video PTS first=${timestamps.first()} last=${timestamps.last()} count=${timestamps.size}")
                // Show the gap between video start and first skeleton
                println("DEBUG: gap from recording start to first skeleton: ${savedKeys.first() - capturedVideoStart}ms")

                // Extract ALL frames and draw the same skeleton on each
                val results = mutableListOf<ComparisonResult>()
                var expectedPageIdx = 0

                extractFrames(savedPath, timestamps).collect { inputFrame ->
                    val frameBitmap = inputFrame.toImageBitmap()
                    val frameElapsed = inputFrame.timestamp - firstTs

                    val output = ImageBitmap(frameBitmap.width, frameBitmap.height)
                    val canvas = Canvas(output)
                    val drawScope = CanvasDrawScope()
                    drawScope.draw(Density(1f), LayoutDirection.Ltr, canvas, Size(frameBitmap.width.toFloat(), frameBitmap.height.toFloat())) {
                        drawImage(frameBitmap)
                        val fw = frameBitmap.width.toFloat(); val fh = frameBitmap.height.toFloat()
                        val sA = liveSkel.width / liveSkel.height; val fA = fw / fh
                        val coX: Float; val coY: Float; val sx: Float; val sy: Float
                        if (kotlin.math.abs(sA - fA) > 0.01f) {
                            if (sA > fA) { sy = fh / liveSkel.height; val vW = liveSkel.height * fA; coX = (liveSkel.width - vW) / 2f; sx = fw / vW; coY = 0f }
                            else { sx = fw / liveSkel.width; val vH = liveSkel.width / fA; coY = (liveSkel.height - vH) / 2f; sy = fh / vH; coX = 0f }
                        } else { sx = fw / liveSkel.width; sy = fh / liveSkel.height; coX = 0f; coY = 0f }
                        fun pt(c: Skeleton.SkeletonCoordinate?) = c?.let { androidx.compose.ui.geometry.Offset((it.x - coX) * sx, (it.y - coY) * sy) }
                        val conns = listOf(liveSkel.leftShoulder to liveSkel.leftElbow, liveSkel.leftElbow to liveSkel.leftWrist, liveSkel.rightShoulder to liveSkel.rightElbow, liveSkel.rightElbow to liveSkel.rightWrist, liveSkel.leftShoulder to liveSkel.rightShoulder, liveSkel.leftShoulder to liveSkel.leftHip, liveSkel.rightShoulder to liveSkel.rightHip, liveSkel.leftHip to liveSkel.rightHip, liveSkel.leftHip to liveSkel.leftKnee, liveSkel.leftKnee to liveSkel.leftAnkle, liveSkel.rightHip to liveSkel.rightKnee, liveSkel.rightKnee to liveSkel.rightAnkle)
                        for ((f, t) in conns) { val p1 = pt(f); val p2 = pt(t); if (p1 != null && p2 != null) drawLine(Color.Blue, p1, p2, strokeWidth = 4f) }
                        for (j in listOfNotNull(liveSkel.leftShoulder, liveSkel.rightShoulder, liveSkel.leftElbow, liveSkel.rightElbow, liveSkel.leftWrist, liveSkel.rightWrist, liveSkel.leftHip, liveSkel.rightHip, liveSkel.leftKnee, liveSkel.rightKnee, liveSkel.leftAnkle, liveSkel.rightAnkle)) { pt(j)?.let { drawCircle(Color.Blue, 6f, it) } }
                        // Draw objects (green rectangles) with same aspect ratio correction
                        for (obj in liveObjects) {
                            val ofw = obj.frameSize.width.toFloat()
                            val ofh = obj.frameSize.height.toFloat()
                            val oA = ofw / ofh
                            val ocX: Float; val ocY: Float; val osx: Float; val osy: Float
                            if (kotlin.math.abs(oA - fA) > 0.01f) {
                                if (oA > fA) { osy = fh / ofh; val vW = ofh * fA; ocX = (ofw - vW) / 2f; osx = fw / vW; ocY = 0f }
                                else { osx = fw / ofw; val vH = ofw / fA; ocY = (ofh - vH) / 2f; osy = fh / vH; ocX = 0f }
                            } else { osx = fw / ofw; osy = fh / ofh; ocX = 0f; ocY = 0f }
                            val left = (obj.boundingBox.left - ocX) * osx
                            val top = (obj.boundingBox.top - ocY) * osy
                            val right = (obj.boundingBox.right - ocX) * osx
                            val bottom = (obj.boundingBox.bottom - ocY) * osy
                            drawRect(Color.Green, topLeft = androidx.compose.ui.geometry.Offset(left, top), size = Size(right - left, bottom - top), style = Stroke(3f))
                        }
                    }

                    if (frameElapsed <= skelVideoTs) expectedPageIdx = results.size
                    val offset = frameElapsed - skelVideoTs
                    results.add(ComparisonResult(
                        image = output,
                        label = "${frameElapsed}ms (${if (offset >= 0) "+" else ""}${offset}ms)",
                        frameTs = inputFrame.timestamp, frameElapsedMs = frameElapsed,
                        expectedElapsedMs = skelVideoTs, liveSkeleton = liveSkel,
                        videoPath = savedPath, allTimestamps = timestamps,
                        angleMatch = -1f, posMatch = -1f
                    ))
                    statusText = "Frame ${results.size}/${timestamps.size}..."
                }

                println("DEBUG: ${results.size} frames, expected page=$expectedPageIdx (${skelVideoTs}ms)")
                comparisonImages = results
                findBestResult = expectedPageIdx to emptyList()
                phase = "DONE"
                statusText = "Skeleton from ${skelVideoTs}ms — swipe to find match"
            } // end of launch(Dispatchers.Default)
        }
    }

    Column(modifier = Modifier.fillMaxSize()) {
        // Status bar
        Text(
            text = statusText,
            modifier = Modifier.fillMaxWidth().padding(8.dp),
            textAlign = TextAlign.Center,
            style = androidx.compose.material3.MaterialTheme.typography.titleSmall,
        )

        // Timing info
        if (phase == "DONE") {
            val recordSec = (recordTimeMs / 100.0).roundToLong() / 10.0
            val extractSec = (extractTimeMs / 100.0).roundToLong() / 10.0
            val fps = if (extractTimeMs > 0) (frameCount * 1000.0 / extractTimeMs).roundToLong() else 0
            Text(
                text = "Record: ${recordSec}s | Extract+encode: ${extractSec}s ($frameCount frames, ~${fps} fps)",
                modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
                textAlign = TextAlign.Center,
                fontSize = 12.sp,
            )
        }

        when (phase) {
            "WAITING_PERMISSION" -> {
                Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
                    CircularProgressIndicator()
                }
            }
            "PREVIEW", "RECORDING" -> {
                if (permissionGranted) {
                    var menuExpanded by remember { mutableStateOf(false) }
                    DetectOrientation { orientation ->
                        Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
                            key(orientation, detectMode) {
                                CameraView(
                                    skeletonRepository = skeletonRepository,
                                    customObjectRepository = customObjectRepository,
                                    detectMode = detectMode,
                                    drawSkeleton = true,
                                    objectModel = generalModel,
                                    modifier = Modifier.fillMaxSize()
                                        .onSizeChanged { size ->
                                            cameraViewPxW = size.width.toFloat()
                                            cameraViewPxH = size.height.toFloat()
                                        },
                                    frontCamera = false,
                                    recordingId = recordingId,
                                    controller = controller,
                                    onRecordToggled = { recording ->
                                        recordingActive = recording
                                        if (recording) {
                                            val callbackWallMs = System.currentTimeMillis()
                                            videoStartWallClock = callbackWallMs
                                            println("RECORD-TIMING: onRecordToggled(true) wallMs=$callbackWallMs")
                                            controller.requestData { data ->
                                                feedWidth = data.width
                                                feedHeight = data.height
                                            }
                                        }
                                    },
                                    onVideoSaved = { id, url ->
                                        videoSavedDeferred.value?.complete(url)
                                    },
                                )
                            }
                            // Menu overlay (same as CameraSample)
                            if (phase == "PREVIEW") {
                                Box(modifier = Modifier.padding(12.dp).align(Alignment.TopEnd)) {
                                    IconButton(onClick = { menuExpanded = true }) {
                                        Text("⋮", color = Color.White, fontSize = 22.sp)
                                    }
                                    DropdownMenu(expanded = menuExpanded, onDismissRequest = { menuExpanded = false }) {
                                        // Detection mode
                                        DropdownMenuItem(text = {
                                            Column {
                                                Text("Detection Mode", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
                                                listOf("Pose" to DetectMode.POSE, "Objects" to DetectMode.OBJECT, "Both" to DetectMode.BOTH, "None" to DetectMode.NONE).forEach { (label, mode) ->
                                                    Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
                                                        Text(label); Spacer(Modifier.width(12.dp))
                                                        RadioButton(selected = detectMode == mode, onClick = { detectMode = mode })
                                                    }
                                                }
                                            }
                                        }, onClick = {})
                                        HorizontalDivider()
                                        // Model picker
                                        DropdownMenuItem(text = {
                                            Column {
                                                Text("Model", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
                                                availableModels.forEach { model ->
                                                    Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
                                                        Text(model.name, fontSize = 13.sp); Spacer(Modifier.width(12.dp))
                                                        RadioButton(selected = selectedModel == model, onClick = { selectedModel = model })
                                                    }
                                                }
                                            }
                                        }, onClick = {})
                                        HorizontalDivider()
                                        // Record button
                                        DropdownMenuItem(
                                            text = { Text("Record 1.5 seconds") },
                                            onClick = { menuExpanded = false; phase = "RECORDING" }
                                        )
                                    }
                                }
                            }
                        }
                    }
                }
            }
            "EXTRACTING" -> {
                Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
                    CircularProgressIndicator()
                }
            }
            "COMPARING" -> {
                Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
                    CircularProgressIndicator()
                }
            }
            "DONE" -> {
                if (comparisonImages.isNotEmpty()) {
                    val initialPage = findBestResult?.first ?: 0
                    val pagerState = rememberPagerState(initialPage = initialPage, pageCount = { comparisonImages.size })
                    Column(
                        modifier = Modifier.weight(1f).fillMaxWidth().padding(8.dp),
                        verticalArrangement = Arrangement.spacedBy(4.dp)
                    ) {
                        val comp = comparisonImages.getOrNull(pagerState.currentPage)
                        val lagText = ""
                        Text(
                            "${lagText}${pagerState.currentPage + 1}/${comparisonImages.size} — ${comp?.label ?: ""}",
                            fontSize = 12.sp, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth()
                        )
                        HorizontalPager(
                            state = pagerState,
                            modifier = Modifier.weight(1f).fillMaxWidth()
                        ) { page ->
                            val c = comparisonImages[page]
                            Image(
                                bitmap = c.image,
                                contentDescription = c.label,
                                contentScale = ContentScale.Fit,
                                modifier = Modifier.fillMaxSize()
                            )
                        }
                        Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                            androidx.compose.material3.Button(
                                onClick = {
                                    val c = comparisonImages.getOrNull(pagerState.currentPage)
                                    if (c != null) {
                                        val lagMs = c.frameElapsedMs - c.expectedElapsedMs
                                        println("BEST-MATCH: lagMs=$lagMs frameElapsed=${c.frameElapsedMs}ms expected=${c.expectedElapsedMs}ms mode=$detectMode model=${selectedModel?.name} page=${pagerState.currentPage}/${comparisonImages.size}")
                                    }
                                },
                                modifier = Modifier.weight(1f)
                            ) { Text("Best Match") }
                            androidx.compose.material3.Button(
                                onClick = {
                                    comparisonImages = emptyList()
                                    findBestResult = null
                                    phase = "PREVIEW"
                                    statusText = "Tap Record to start"
                                },
                                modifier = Modifier.weight(1f)
                            ) { Text("New Recording") }
                        }
                    }
                } else {
                    Box(modifier = Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
                        Text("No comparison images produced")
                    }
                }
            }
        }
    }
}

@OptIn(ExperimentalTime::class)
@Composable
fun CameraSample() {
    val availableModels = discoverModels()
    val skeletonRepository = remember { SkeletonRepository() }
    val customObjectRespository = remember { CustomObjectRespository() }
    val skeleton by skeletonRepository.skeletonFlow.collectAsState()
    val customObjects by customObjectRespository.customObjectFlow.collectAsState()
    var permissionGranted by remember { mutableStateOf(false) }
    var recordingId: String? by remember { mutableStateOf(null) }
    var path by remember { mutableStateOf("") }
    var selectedModel by remember(availableModels) { mutableStateOf(availableModels.firstOrNull()) }
    val generalModel = rememberObjectModel(selectedModel?.path ?: ModelPath())
    var frontCamera by remember { mutableStateOf(false) }
    var ultrawide by remember { mutableStateOf(false) }
    var previewFillMode by remember { mutableStateOf(PreviewFillMode.FIT) }
    var menuExpanded by remember { mutableStateOf(false) }
    var detectMode by remember { mutableStateOf(DetectMode.BOTH) }
    val controller = remember { CameraViewControllerImpl() }
    PermissionProvider().apply {
        if (!hasCameraPermission()) RequestCameraPermission(onGranted = {
            permissionGranted = true
        }, onDenied = { permissionGranted = false }) else permissionGranted = true
    }

    if (permissionGranted) {
        DetectOrientation { orientation ->
            Box(modifier = Modifier.fillMaxSize()) {
                Column(modifier = Modifier.fillMaxSize()) {
                    androidx.compose.animation.AnimatedVisibility(path.isNotBlank()) {
                        val playerHost = remember(path) {
                            MediaPlayerHost(
                                mediaUrl = path,
                                isLooping = true,
                                isPaused = false,
                                isMuted = false,
                                initialVideoFitMode = ScreenResize.FIT,
                            )
                        }
                        VideoPlayerComposable(
                            modifier = Modifier.weight(1f),
                            playerHost = playerHost,
                            playerConfig = VideoPlayerConfig(
                                isSeekBarVisible = true,
                                isDurationVisible = true,
                                isFastForwardBackwardEnabled = true,
                                isMuteControlEnabled = true,
                                isSpeedControlEnabled = true,
                                isScreenLockEnabled = false,
                                isScreenResizeEnabled = true,
                                isFullScreenEnabled = true,
                                isPauseResumeEnabled = true,
                            )
                        )
                    }
                    key(orientation) {
                        CameraView(
                            skeletonRepository = skeletonRepository,
                            customObjectRepository = customObjectRespository,
                            detectMode = detectMode,
                            drawSkeleton = true,
                            drawObjects = { obj ->
                                obj.flatMap {
                                    listOf(
                                        DrawableObject(
                                            obj = it,
                                            shape = DrawableShape.LABEL,
                                            colour = Color.Yellow,
                                            style = Stroke(it.boundingBox.width * 0.1f)
                                        ), DrawableObject(
                                            obj = it,
                                            shape = DrawableShape.RECTANGLE,
                                            colour = Color.Green,
                                            style = Stroke(it.boundingBox.width * 0.1f)
                                        )
                                    )
                                }
                            },
                            objectModel = generalModel,
                            modifier = Modifier.weight(1f),
                            focusArea = Rect(0f, 0f, 1f, 1f),
                            frontCamera = frontCamera,
                            useUltraWide = ultrawide,
                            previewFillMode = previewFillMode,
                            recordingId = recordingId,
                            controller = controller,
                            onVideoSaved = { id, url -> path = url },
                        )
                    }
                }
                Box(
                    modifier = Modifier
                        .imePadding()
                        .padding(12.dp)
                        .align(Alignment.TopEnd)
                ) {
                    IconButton(onClick = { menuExpanded = true }) {
                        Text("⋮", color = Color.White, fontSize = 22.sp)
                    }

                    DropdownMenu(
                        expanded = menuExpanded,
                        onDismissRequest = { menuExpanded = false }
                    ) {
                        // Zoom toggle
                        DropdownMenuItem(
                            text = {
                                Row(
                                    horizontalArrangement = Arrangement.SpaceBetween,
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text(text = if (ultrawide) "0.5x zoom" else "1.0x zoom")
                                    Spacer(Modifier.width(12.dp))
                                    Switch(checked = ultrawide, onCheckedChange = null)
                                }
                            },
                            onClick = { ultrawide = !ultrawide }
                        )

                        // Preview fill/crop toggle
                        DropdownMenuItem(
                            text = {
                                Row(
                                    horizontalArrangement = Arrangement.SpaceBetween,
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text(text = if (previewFillMode == PreviewFillMode.FIT) "Fill Preview" else "Fit Preview")
                                    Spacer(Modifier.width(12.dp))
                                    Switch(
                                        checked = previewFillMode == PreviewFillMode.FIT,
                                        onCheckedChange = null
                                    )
                                }
                            },
                            onClick = {
                                previewFillMode =
                                    if (previewFillMode == PreviewFillMode.FIT) PreviewFillMode.FILL else PreviewFillMode.FIT
                            }
                        )

                        HorizontalDivider()

                        DropdownMenuItem(text = {
                            Column {
                                Text(
                                    "Detection Mode",
                                    modifier = Modifier.fillMaxWidth(),
                                    textAlign = TextAlign.Center
                                )
                                Row(
                                    horizontalArrangement = Arrangement.SpaceBetween,
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text(text = "Pose")
                                    Spacer(Modifier.width(12.dp))
                                    RadioButton(
                                        selected = detectMode == DetectMode.POSE,
                                        onClick = {
                                            detectMode = DetectMode.POSE
                                        })
                                }
                                Row(
                                    horizontalArrangement = Arrangement.SpaceBetween,
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text(text = "Objects")
                                    Spacer(Modifier.width(12.dp))
                                    RadioButton(
                                        selected = detectMode == DetectMode.OBJECT,
                                        onClick = {
                                            detectMode = DetectMode.OBJECT
                                        })
                                }
                                Row(
                                    horizontalArrangement = Arrangement.SpaceBetween,
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text(text = "Both")
                                    Spacer(Modifier.width(12.dp))
                                    RadioButton(
                                        selected = detectMode == DetectMode.BOTH,
                                        onClick = {
                                            detectMode = DetectMode.BOTH
                                        })
                                }
                                Row(
                                    horizontalArrangement = Arrangement.SpaceBetween,
                                    verticalAlignment = Alignment.CenterVertically,
                                    modifier = Modifier.fillMaxWidth()
                                ) {
                                    Text(text = "None")
                                    Spacer(Modifier.width(12.dp))
                                    RadioButton(
                                        selected = detectMode == DetectMode.NONE,
                                        onClick = {
                                            detectMode = DetectMode.NONE
                                        })
                                }
                            }
                        }, onClick = {})

                        HorizontalDivider()

                        // Model picker — populated dynamically from bundled resources
                        DropdownMenuItem(text = {
                            Column {
                                Text(
                                    "Model",
                                    modifier = Modifier.fillMaxWidth(),
                                    textAlign = TextAlign.Center
                                )
                                if (availableModels.isEmpty()) {
                                    Text("No models found", fontSize = 12.sp)
                                } else {
                                    availableModels.forEach { model ->
                                        Row(
                                            horizontalArrangement = Arrangement.SpaceBetween,
                                            verticalAlignment = Alignment.CenterVertically,
                                            modifier = Modifier.fillMaxWidth()
                                        ) {
                                            Text(text = model.name, fontSize = 13.sp)
                                            Spacer(Modifier.width(12.dp))
                                            RadioButton(
                                                selected = selectedModel == model,
                                                onClick = { selectedModel = model }
                                            )
                                        }
                                    }
                                }
                            }
                        }, onClick = {})

                        HorizontalDivider()

                        // Start/End recording button
                        DropdownMenuItem(
                            text = { Text(if (recordingId != null) "End recording" else "Start recording") },
                            onClick = {
                                if (recordingId == null) {
                                    recordingId = "${Clock.System.now().epochSeconds}"
                                } else {
                                    recordingId = null
                                }
                                // Keep menu open or close? Close feels better.
                                menuExpanded = false
                            }
                        )
                    }
                }
            }
        }

    } else Text("Camera permission not granted")

    val upRightPose = Pose(
        leftShoulder = Pose.PoseRange(0.0, 40.0),
        rightShoulder = Pose.PoseRange(0.0, 40.0),
        leftHip = Pose.PoseRange(160.0, 180.0),
        rightHip = Pose.PoseRange(160.0, 180.0),
        leftKnee = Pose.PoseRange(160.0, 180.0),
        rightKnee = Pose.PoseRange(160.0, 180.0)
    )
    val starPose = Pose(
        leftShoulder = Pose.PoseRange(70.0, 110.0),
        rightShoulder = Pose.PoseRange(70.0, 110.0),
        leftHip = Pose.PoseRange(110.0, 150.0),
        rightHip = Pose.PoseRange(110.0, 150.0),
        leftKnee = Pose.PoseRange(160.0, 180.0),
        rightKnee = Pose.PoseRange(160.0, 180.0)
    )
    skeleton?.let {
        if (upRightPose.matches(it)) Text("Standing", fontSize = 15.sp)
        else if (starPose.matches(it)) Text("Star", fontSize = 15.sp)
        else Text("No Pose Detected", fontSize = 15.sp)
    }
}
