package com.nate.posedetection

import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.Button
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.material3.CircularProgressIndicator
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.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.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.layout.ContentScale
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 co.touchlab.kermit.Logger
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.custom.CustomObjectRespository
import com.performancecoachlab.posedetection.custom.ModelPath
import com.performancecoachlab.posedetection.custom.initialiseObjectModel
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.FrameSize
import com.performancecoachlab.posedetection.recording.InputFrame
import com.performancecoachlab.posedetection.recording.Label
import com.performancecoachlab.posedetection.recording.extractFrame
import com.performancecoachlab.posedetection.recording.listVideoFrameTimestamps
import com.performancecoachlab.posedetection.skeleton.Pose
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.Job
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")
    Column(
        modifier = Modifier
        .fillMaxSize()
        .windowInsetsPadding(WindowInsets.safeDrawing)
    ) {
        TabRow(selectedTabIndex = selectedTabIndex) {
            tabs.forEachIndexed { index, title ->
                Tab(
                    selected = selectedTabIndex == index,
                    onClick = { selectedTabIndex = index },
                    text = { Text(title) })
            }
        }
        when (selectedTabIndex) {
            0 -> CameraSample()
            1 -> RecordedSample()
        }
    }
}

@Composable
fun RecordedSample() {
    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)) {
        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")
        }
        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
                    )
                }

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

@Composable
fun FrameAnalysis(
    url: String, modifier: Modifier = Modifier, duration: Long
) {
    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 generalModel = initialiseObjectModel(
        ModelPath(
            "lite-model_efficientdet_lite2_detection_metadata_1.tflite",
            "YOLOv3FP16"
        )
    )
    val frameAnalyser by remember { 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) }
    lateinit var size: Pair<Int, Int>
    var firstFrameTimestamp: Long? by remember { mutableStateOf(null) }

    // Function to start recording
    fun startRecording() {
        isRecording = true
        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()
                    //launcher.launch(file)
                    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()) {
        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),
            )
        }
    }
}

@OptIn(ExperimentalTime::class)
@Composable
fun CameraSample() {
    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("") }
    val generalModel = initialiseObjectModel(
        ModelPath(
            "lite-model_efficientdet_lite2_detection_metadata_1.tflite",
            "YOLOv3FP16"
        )
    )
    val controller = remember { CameraViewControllerImpl() }
    PermissionProvider().apply {
        if (!hasCameraPermission()) RequestCameraPermission(onGranted = {
            permissionGranted = true
        }, onDenied = { permissionGranted = false }) else permissionGranted = true
    }
    if (permissionGranted) {
        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,
                        )
                    )
                }
                CameraView(
                    skeletonRepository = skeletonRepository,
                    customObjectRepository = customObjectRespository,
                    detectMode = DetectMode.OBJECT,
                    drawSkeleton = true,
                    drawObjects = { obj ->
                        obj.map {
                            DrawableObject(
                                obj = it,
                                shape = DrawableShape.RECTANGLE,
                                colour = Color.Yellow,
                                style = Stroke(it.boundingBox.width * 0.1f)
                            )
                        }
                    },
                    objectModel = generalModel,
                    modifier = Modifier.weight(1f),
                    focusArea = Rect(0f,0f,0.1f,1f),
                    frontCamera = false,
                    recordingId = recordingId,
                    controller = controller,
                    onVideoSaved = {id,url -> path = url },
                )
            }
            Button(
                onClick = {
                    //recordingId = "${Clock.System.now().epochSeconds}"
                    controller.requestData { data ->
                        Logger.d("CameraViewData: $data")
                    }
                },
                modifier = Modifier.imePadding().padding(16.dp).align(Alignment.TopStart)
            ) {
                Text("Start Recording")
            }
        }
    } 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 = 80.sp)
        else if (starPose.matches(it)) Text("Star", fontSize = 80.sp)
        else Text("No Pose Detected", fontSize = 80.sp)
    }
}
