package de.softwareapp_hb.privateqrscanner.ui.components

import android.annotation.SuppressLint
import android.util.Size
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.core.resolutionselector.ResolutionSelector
import androidx.camera.core.resolutionselector.ResolutionStrategy
import androidx.camera.core.UseCaseGroup
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import de.softwareapp_hb.privateqrscanner.data.scanner.DetectionBox
import de.softwareapp_hb.privateqrscanner.data.scanner.MlKitBarcodeAnalyzer
import de.softwareapp_hb.privateqrscanner.domain.ScanResult
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import kotlin.math.max
import kotlin.math.min

@SuppressLint("UnsafeOptInUsageError")
@Composable
fun CameraPreview(
    modifier: Modifier = Modifier,
    analysisEnabled: Boolean,
    torchEnabled: Boolean,
    onTorchAvailabilityChanged: (Boolean) -> Unit,
    onDetectionStateChanged: (
        hasPotential: Boolean,
        hasReadable: Boolean,
        boxes: List<DetectionBox>,
        sourceWidth: Int,
        sourceHeight: Int
    ) -> Unit = { _, _, _, _, _ -> },
    onScan: (ScanResult, DetectionBox?, Int, Int) -> Unit
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val cameraExecutor: ExecutorService = remember { Executors.newSingleThreadExecutor() }
    val mainExecutor = remember(context) { ContextCompat.getMainExecutor(context) }
    val previewView = remember { PreviewView(context) }
    val cameraRef = remember { mutableStateOf<androidx.camera.core.Camera?>(null) }
    val zoomRatio = remember { mutableFloatStateOf(1f) }
    val latestAnalysisEnabled = rememberUpdatedState(analysisEnabled)
    val latestOnScan = rememberUpdatedState(onScan)
    val latestOnDetectionStateChanged = rememberUpdatedState(onDetectionStateChanged)
    val analyzer = remember {
        MlKitBarcodeAnalyzer(
            isAnalysisEnabled = { latestAnalysisEnabled.value },
            onDetectionStateChanged = { hasPotential, hasReadable, boxes, sourceWidth, sourceHeight ->
                mainExecutor.execute {
                    latestOnDetectionStateChanged.value(
                        hasPotential,
                        hasReadable,
                        boxes,
                        sourceWidth,
                        sourceHeight
                    )
                }
            },
            onDetected = { result, readableBox, sourceWidth, sourceHeight ->
                latestOnScan.value(result, readableBox, sourceWidth, sourceHeight)
            }
        )
    }
    val cameraProviderRef = remember { mutableStateOf<ProcessCameraProvider?>(null) }

    val scaleGestureDetector = remember {
        ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
            override fun onScale(detector: ScaleGestureDetector): Boolean {
                val camera = cameraRef.value ?: return false
                val zoomState = camera.cameraInfo.zoomState.value ?: return false
                val nextZoom = zoomRatio.floatValue * detector.scaleFactor
                val clampedZoom = max(zoomState.minZoomRatio, min(nextZoom, zoomState.maxZoomRatio))
                zoomRatio.floatValue = clampedZoom
                camera.cameraControl.setZoomRatio(clampedZoom)
                return true
            }
        })
    }

    DisposableEffect(Unit) {
        onDispose {
            cameraProviderRef.value?.unbindAll()
            analyzer.close()
            cameraExecutor.shutdown()
        }
    }

    LaunchedEffect(lifecycleOwner) {
        val provider = ProcessCameraProvider.getInstance(context).get()
        cameraProviderRef.value = provider
        provider.unbindAll()

        val preview = Preview.Builder().build().apply {
            surfaceProvider = previewView.surfaceProvider
        }

        val imageAnalysis = ImageAnalysis.Builder()
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .setResolutionSelector(
                ResolutionSelector.Builder()
                    .setResolutionStrategy(
                        ResolutionStrategy(
                            Size(1280, 720),
                            ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER
                        )
                    )
                    .build()
            )
            .build().apply {
                setAnalyzer(cameraExecutor, analyzer)
            }

        val camera = previewView.viewPort?.let { viewPort ->
            val group = UseCaseGroup.Builder()
                .setViewPort(viewPort)
                .addUseCase(preview)
                .addUseCase(imageAnalysis)
                .build()
            provider.bindToLifecycle(
                lifecycleOwner,
                CameraSelector.DEFAULT_BACK_CAMERA,
                group
            )
        } ?: provider.bindToLifecycle(
            lifecycleOwner,
            CameraSelector.DEFAULT_BACK_CAMERA,
            preview,
            imageAnalysis
        )

        onTorchAvailabilityChanged(camera.cameraInfo.hasFlashUnit())
        cameraRef.value = camera
        zoomRatio.floatValue = camera.cameraInfo.zoomState.value?.zoomRatio ?: 1f
    }

    LaunchedEffect(torchEnabled) {
        cameraRef.value?.cameraControl?.enableTorch(torchEnabled)
    }

    AndroidView(
        modifier = modifier,
        factory = {
            previewView.apply {
                scaleType = PreviewView.ScaleType.FILL_CENTER
                setOnTouchListener { _, event ->
                    if (event.actionMasked == MotionEvent.ACTION_DOWN) {
                        performClick()
                    }
                    scaleGestureDetector.onTouchEvent(event)
                    true
                }
            }
        }
    )
}
