package com.example.studentfaceregistry

import android.Manifest
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Typeface
import android.os.Bundle
import android.os.SystemClock
import android.text.InputType
import android.util.Size
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.EditText
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.example.studentfaceregistry.data.Student
import com.example.studentfaceregistry.data.StudentRepository
import com.example.studentfaceregistry.face.FaceMatcher
import com.example.studentfaceregistry.face.FaceProcessor
import com.example.studentfaceregistry.face.MatchType
import com.example.studentfaceregistry.face.RecognitionResult
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.face.Face
import com.example.studentfaceregistry.ui.DetectionUi
import com.example.studentfaceregistry.ui.OverlayView
import com.example.studentfaceregistry.upload.UploadServer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors

@ExperimentalGetImage
class MainActivity : AppCompatActivity() {
    private lateinit var countText: TextView
    private lateinit var previewView: PreviewView
    private lateinit var overlayView: OverlayView
    private lateinit var recognitionStatusText: TextView
    private lateinit var matcherConfigSummaryText: TextView
    private lateinit var euclideanThresholdInput: EditText
    private lateinit var cosineThresholdInput: EditText
    private lateinit var uploadStatusText: TextView
    private lateinit var uploadUrlText: TextView
    private lateinit var pauseRecognitionButton: Button
    private lateinit var homePanel: View
    private lateinit var recognitionPanel: View
    private lateinit var uploadPanel: View

    private var currentMode = AppMode.HOME
    private var processor: FaceProcessor? = null
    private var uploadServer: UploadServer? = null
    private var analysisUseCase: ImageAnalysis? = null
    private var cameraProvider: ProcessCameraProvider? = null

    private val repository by lazy { StudentRepository(this) }
    private val matcherPrefs: SharedPreferences by lazy {
        getSharedPreferences(MATCHER_PREFS_NAME, MODE_PRIVATE)
    }
    private val cameraExecutor = Executors.newSingleThreadExecutor()
    private var students: List<Student> = emptyList()
    private var recognitionEnabled = true
    private var analyzing = false
    private var lastAnalysisAt = 0L
    private var euclideanThreshold = DEFAULT_EUCLIDEAN_THRESHOLD
    private var cosineThreshold = DEFAULT_COSINE_THRESHOLD
    @Volatile private var matcher = FaceMatcher(DEFAULT_EUCLIDEAN_THRESHOLD, DEFAULT_COSINE_THRESHOLD)
    private val recognitionHistory = mutableMapOf<Int, ArrayDeque<RecognitionResult>>()
    private val recognitionCache = mutableMapOf<Int, CachedRecognition>()
    private var lastVisibleDetections: List<DetectionUi> = emptyList()
    private var lastVisibleDetectionsAt = 0L

    private val permissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { grants ->
        if (grants[Manifest.permission.CAMERA] == true) {
            if (currentMode == AppMode.RECOGNITION) {
                startRecognitionSession()
            }
        } else if (currentMode == AppMode.RECOGNITION) {
            recognitionStatusText.text = "需要相机权限才能识别学生身份。"
            toast("需要相机权限才能识别学生身份。")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        loadMatcherConfig()
        setContentView(createContentView())

        lifecycleScope.launch {
            repository.students.collectLatest { list ->
                students = list
                countText.text = "已入库 ${list.size} 名学生"
            }
        }

        switchMode(AppMode.HOME)
    }

    override fun onDestroy() {
        super.onDestroy()
        stopRecognitionSession()
        stopUploadSession()
        cameraExecutor.shutdown()
    }

    private fun createContentView(): View {
        val root = LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            setBackgroundColor(0xFFF8FAFC.toInt())
        }

        val header = LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            setPadding(32, 28, 32, 20)
        }
        header.addView(TextView(this).apply {
            text = "学生人脸识别"
            textSize = 24f
            setTextColor(0xFF0F172A.toInt())
        })
        countText = TextView(this).apply {
            text = "已入库 0 名学生"
            textSize = 15f
            setTextColor(0xFF475569.toInt())
        }
        header.addView(countText)

        val content = FrameLayout(this).apply {
            layoutParams = LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                0,
                1f
            )
        }

        homePanel = createHomePanel()
        recognitionPanel = createRecognitionPanel()
        uploadPanel = createUploadPanel()
        homePanel.visibility = View.VISIBLE
        recognitionPanel.visibility = View.GONE
        uploadPanel.visibility = View.GONE

        content.addView(homePanel, FrameLayout.LayoutParams(-1, -1))
        content.addView(recognitionPanel, FrameLayout.LayoutParams(-1, -1))
        content.addView(uploadPanel, FrameLayout.LayoutParams(-1, -1))

        root.addView(header)
        root.addView(content)
        return root
    }

    private fun createHomePanel(): View {
        return LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            gravity = Gravity.CENTER
            setPadding(40, 40, 40, 40)

            addView(TextView(this@MainActivity).apply {
                text = "请选择要进入的功能"
                textSize = 26f
                typeface = Typeface.DEFAULT_BOLD
                setTextColor(0xFF0F172A.toInt())
                gravity = Gravity.CENTER
            })

            addView(TextView(this@MainActivity).apply {
                text = "识别和注册上传分开运行，减少同时占用相机、模型和本地服务带来的卡顿。"
                textSize = 16f
                setTextColor(0xFF475569.toInt())
                gravity = Gravity.CENTER
                setPadding(0, 20, 0, 36)
            })

            addView(Button(this@MainActivity).apply {
                text = "进入识别"
                textSize = 20f
                setPadding(36, 24, 36, 24)
                setOnClickListener { switchMode(AppMode.RECOGNITION) }
            })

            addView(Button(this@MainActivity).apply {
                text = "进入注册上传"
                textSize = 20f
                setPadding(36, 24, 36, 24)
                setOnClickListener { switchMode(AppMode.UPLOAD) }
            })
        }
    }

    private fun createRecognitionPanel(): View {
        return LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            setPadding(24, 16, 24, 24)

            recognitionStatusText = TextView(this@MainActivity).apply {
                text = "准备进入识别模式"
                textSize = 22f
                typeface = Typeface.DEFAULT_BOLD
                setTextColor(0xFF0F172A.toInt())
                setPadding(8, 8, 8, 20)
            }
            addView(recognitionStatusText)

            val matcherConfigPanel = LinearLayout(this@MainActivity).apply {
                orientation = LinearLayout.VERTICAL
                setPadding(16, 16, 16, 16)
                setBackgroundColor(0xFFF1F5F9.toInt())
            }

            matcherConfigSummaryText = TextView(this@MainActivity).apply {
                textSize = 14f
                setTextColor(0xFF475569.toInt())
                text = matcherConfigSummary()
                setPadding(0, 0, 0, 12)
            }
            matcherConfigPanel.addView(matcherConfigSummaryText)

            val thresholdRow = LinearLayout(this@MainActivity).apply {
                orientation = LinearLayout.HORIZONTAL
            }

            val euclideanColumn = LinearLayout(this@MainActivity).apply {
                orientation = LinearLayout.VERTICAL
                layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
                setPadding(0, 0, 12, 0)
            }
            euclideanColumn.addView(TextView(this@MainActivity).apply {
                text = "欧氏阈值"
                textSize = 13f
                setTextColor(0xFF334155.toInt())
            })
            euclideanThresholdInput = EditText(this@MainActivity).apply {
                setText(formatThreshold(euclideanThreshold))
                hint = "0.00"
                inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
                setTextColor(0xFF0F172A.toInt())
                setBackgroundColor(0xFFFFFFFF.toInt())
                setPadding(20, 18, 20, 18)
            }
            euclideanColumn.addView(euclideanThresholdInput)

            val cosineColumn = LinearLayout(this@MainActivity).apply {
                orientation = LinearLayout.VERTICAL
                layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
            }
            cosineColumn.addView(TextView(this@MainActivity).apply {
                text = "余弦阈值"
                textSize = 13f
                setTextColor(0xFF334155.toInt())
            })
            cosineThresholdInput = EditText(this@MainActivity).apply {
                setText(formatThreshold(cosineThreshold))
                hint = "0.00"
                inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED
                setTextColor(0xFF0F172A.toInt())
                setBackgroundColor(0xFFFFFFFF.toInt())
                setPadding(20, 18, 20, 18)
            }
            cosineColumn.addView(cosineThresholdInput)

            thresholdRow.addView(euclideanColumn)
            thresholdRow.addView(cosineColumn)
            matcherConfigPanel.addView(thresholdRow)

            matcherConfigPanel.addView(Button(this@MainActivity).apply {
                text = "保存识别参数"
                setOnClickListener { saveMatcherConfig() }
            })

            addView(matcherConfigPanel)

            val cameraFrame = FrameLayout(this@MainActivity).apply {
                layoutParams = LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    0,
                    1f
                )
            }
            previewView = PreviewView(this@MainActivity).apply {
                scaleType = PreviewView.ScaleType.FILL_CENTER
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
            }
            overlayView = OverlayView(this@MainActivity)
            cameraFrame.addView(previewView, FrameLayout.LayoutParams(-1, -1))
            cameraFrame.addView(overlayView, FrameLayout.LayoutParams(-1, -1))
            addView(cameraFrame)

            addView(LinearLayout(this@MainActivity).apply {
                gravity = Gravity.CENTER
                orientation = LinearLayout.HORIZONTAL
                setPadding(12, 20, 12, 8)

                addView(Button(this@MainActivity).apply {
                    text = "返回首页"
                    setOnClickListener { switchMode(AppMode.HOME) }
                })

                pauseRecognitionButton = Button(this@MainActivity).apply {
                    text = "暂停识别"
                    setOnClickListener {
                        recognitionEnabled = !recognitionEnabled
                        text = if (recognitionEnabled) "暂停识别" else "继续识别"
                        recognitionStatusText.text = if (recognitionEnabled) "正在识别" else "识别已暂停"
                        if (!recognitionEnabled) {
                            overlayView.update(emptyList())
                        }
                        updateAnalyzerState()
                    }
                }
                addView(pauseRecognitionButton)
            })
        }
    }

    private fun createUploadPanel(): View {
        return LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            gravity = Gravity.CENTER_HORIZONTAL
            setPadding(40, 48, 40, 48)

            uploadStatusText = TextView(this@MainActivity).apply {
                text = "准备进入注册上传模式"
                textSize = 24f
                typeface = Typeface.DEFAULT_BOLD
                setTextColor(0xFF0F172A.toInt())
                gravity = Gravity.CENTER
            }
            addView(uploadStatusText)

            addView(TextView(this@MainActivity).apply {
                text = "在同一 Wi-Fi 下，用电脑浏览器打开下面的地址，就可以上传学生图片或视频。"
                textSize = 16f
                setTextColor(0xFF475569.toInt())
                gravity = Gravity.CENTER
                setPadding(0, 16, 0, 20)
            })

            uploadUrlText = TextView(this@MainActivity).apply {
                text = "-"
                textSize = 20f
                typeface = Typeface.MONOSPACE
                setTextColor(0xFF0F172A.toInt())
                gravity = Gravity.CENTER
                setPadding(24, 20, 24, 20)
                setTextIsSelectable(true)
                setBackgroundColor(0xFFE2E8F0.toInt())
            }
            addView(uploadUrlText)

            addView(LinearLayout(this@MainActivity).apply {
                gravity = Gravity.CENTER
                orientation = LinearLayout.HORIZONTAL
                setPadding(12, 28, 12, 8)

                addView(Button(this@MainActivity).apply {
                    text = "刷新地址"
                    setOnClickListener { startUploadSession() }
                })

                addView(Button(this@MainActivity).apply {
                    text = "返回首页"
                    setOnClickListener { switchMode(AppMode.HOME) }
                })
            })
        }
    }

    private fun switchMode(mode: AppMode) {
        if (currentMode == mode) return

        val previousMode = currentMode
        currentMode = mode
        homePanel.visibility = if (mode == AppMode.HOME) View.VISIBLE else View.GONE
        recognitionPanel.visibility = if (mode == AppMode.RECOGNITION) View.VISIBLE else View.GONE
        uploadPanel.visibility = if (mode == AppMode.UPLOAD) View.VISIBLE else View.GONE

        when (previousMode) {
            AppMode.RECOGNITION -> stopRecognitionSession()
            AppMode.UPLOAD -> stopUploadSession()
            AppMode.HOME -> Unit
        }

        when (mode) {
            AppMode.HOME -> Unit
            AppMode.RECOGNITION -> enterRecognitionMode()
            AppMode.UPLOAD -> enterUploadMode()
        }
    }

    private fun enterRecognitionMode() {
        recognitionEnabled = true
        analyzing = false
        lastAnalysisAt = 0L
        pauseRecognitionButton.text = "暂停识别"
        recognitionStatusText.text = "正在准备识别服务..."
        matcherConfigSummaryText.text = matcherConfigSummary()
        euclideanThresholdInput.setText(formatThreshold(euclideanThreshold))
        cosineThresholdInput.setText(formatThreshold(cosineThreshold))
        recognitionHistory.clear()
        recognitionCache.clear()
        lastVisibleDetections = emptyList()
        lastVisibleDetectionsAt = 0L
        overlayView.update(emptyList())

        if (hasCameraPermission()) {
            startRecognitionSession()
        } else {
            permissionLauncher.launch(arrayOf(Manifest.permission.CAMERA))
        }
    }

    private fun startRecognitionSession() {
        if (currentMode != AppMode.RECOGNITION) return

        val faceProcessor = ensureProcessor() ?: return
        previewView.post {
            val providerFuture = ProcessCameraProvider.getInstance(this)
            providerFuture.addListener({
                val provider = providerFuture.get()
                cameraProvider = provider
                if (currentMode != AppMode.RECOGNITION) {
                    provider.unbindAll()
                    return@addListener
                }

                val preview = Preview.Builder()
                    .setTargetResolution(ANALYSIS_SIZE)
                    .build()
                    .also { it.setSurfaceProvider(previewView.surfaceProvider) }
                val analysis = ImageAnalysis.Builder()
                    .setTargetResolution(ANALYSIS_SIZE)
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()

                val viewPort = previewView.viewPort
                provider.unbindAll()
                if (viewPort != null) {
                    val group = androidx.camera.core.UseCaseGroup.Builder()
                        .setViewPort(viewPort)
                        .addUseCase(preview)
                        .addUseCase(analysis)
                        .build()
                    provider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, group)
                } else {
                    provider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, analysis)
                }
                analysisUseCase = analysis
                recognitionStatusText.text = if (faceProcessor === processor) "正在识别" else "识别服务已启动"
                updateAnalyzerState()
            }, ContextCompat.getMainExecutor(this))
        }
    }

    private fun stopRecognitionSession() {
        recognitionEnabled = false
        analyzing = false
        analysisUseCase?.clearAnalyzer()
        analysisUseCase = null
        cameraProvider?.unbindAll()
        cameraProvider = null
        recognitionHistory.clear()
        recognitionCache.clear()
        lastVisibleDetections = emptyList()
        lastVisibleDetectionsAt = 0L
        overlayView.update(emptyList())
        processor?.close()
        processor = null
    }

    private fun updateAnalyzerState() {
        val analysis = analysisUseCase ?: return
        if (currentMode == AppMode.RECOGNITION && recognitionEnabled && processor != null) {
            analysis.setAnalyzer(cameraExecutor) { image -> analyze(image) }
        } else {
            analysis.clearAnalyzer()
        }
    }

    private fun analyze(image: ImageProxy) {
        val faceProcessor = processor
        val now = SystemClock.elapsedRealtime()
        if (
            currentMode != AppMode.RECOGNITION ||
            !recognitionEnabled ||
            analyzing ||
            faceProcessor == null ||
            now - lastAnalysisAt < ANALYSIS_INTERVAL_MS
        ) {
            image.close()
            return
        }

        analyzing = true
        lastAnalysisAt = now
        lifecycleScope.launch(Dispatchers.Default) {
            try {
                val mediaImage = image.image ?: return@launch
                val inputImage = InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees)
                val faces = faceProcessor.detectFaces(inputImage)

                if (faces.isEmpty()) {
                    withContext(Dispatchers.Main) {
                        if (currentMode == AppMode.RECOGNITION) {
                            if (now - lastVisibleDetectionsAt <= DETECTION_GRACE_MS && lastVisibleDetections.isNotEmpty()) {
                                overlayView.update(lastVisibleDetections)
                                updateRecognitionStatus(lastVisibleDetections)
                            } else {
                                recognitionHistory.clear()
                                recognitionCache.clear()
                                lastVisibleDetections = emptyList()
                                lastVisibleDetectionsAt = 0L
                                overlayView.update(emptyList())
                                updateRecognitionStatus(emptyList())
                            }
                        }
                    }
                    return@launch
                }

                val activeTrackIds = faces.mapNotNull { it.trackingId }.toSet()
                val facesNeedingRefresh = faces.mapIndexedNotNull { index, face ->
                    if (shouldRefreshRecognition(face, now)) index to face else null
                }
                val refreshedResults = mutableMapOf<Int, RecognitionResult>()

                if (facesNeedingRefresh.isNotEmpty()) {
                    val bitmap = ImageUtils.imageProxyToBitmap(image)
                    facesNeedingRefresh.forEach { (index, face) ->
                        val refreshed = refreshRecognition(faceProcessor, bitmap, face)
                        refreshedResults[index] = refreshed
                    }
                }

                val rawDetections = faces.mapIndexed { index, face ->
                    val result = resolveRecognitionResult(face, refreshedResults[index], now)
                    RawDetection(
                        bounds = face.boundingBox,
                        result = result,
                        confidence = if (result.student != null) result.confidence else null
                    )
                }
                pruneTrackCache(now, activeTrackIds)

                withContext(Dispatchers.Main) {
                    if (currentMode == AppMode.RECOGNITION) {
                        val detections = mapDetectionsToPreview(image, rawDetections)
                        overlayView.update(detections)
                        updateRecognitionStatus(detections)
                        lastVisibleDetections = detections
                        lastVisibleDetectionsAt = now
                    }
                }
            } catch (e: Exception) {
                Log.e("MainActivity", "Recognition failed", e)
                withContext(Dispatchers.Main) {
                    if (currentMode == AppMode.RECOGNITION) {
                        recognitionStatusText.text = "识别失败：${e.message ?: e.javaClass.simpleName}"
                    }
                }
            } finally {
                analyzing = false
                image.close()
            }
        }
    }

    private fun updateRecognitionStatus(detections: List<DetectionUi>) {
        if (detections.isEmpty()) {
            recognitionStatusText.text = "未检测到人脸"
            return
        }

        val matched = detections.filter { it.result.isMatched }
        val unsure = detections.filter { it.result.matchType == MatchType.UNSURE }
        val tracking = detections.count { it.result.matchType == MatchType.NO_DATA }
        val noMatch = detections.count { it.result.matchType == MatchType.NO_MATCH }
        val total = detections.size

        if (matched.isNotEmpty()) {
            if (matched.size == 1 && total == 1) {
                val student = matched[0].result.student!!
                val confidence = (matched[0].confidence!! * 100).toInt()
                recognitionStatusText.text = "识别到：${student.name}（${student.studentNo}）置信度${confidence}%"
            } else {
                val names = matched.joinToString(", ") {
                    val student = it.result.student!!
                    val confidence = (it.confidence!! * 100).toInt()
                    "${student.name}(${confidence}%)"
                }
                val remaining = total - matched.size
                recognitionStatusText.text = if (remaining > 0) {
                    "识别到 ${matched.size}/$total 人：$names，另有 $remaining 人跟踪中"
                } else {
                    "识别到 ${matched.size}/$total 人：$names"
                }
            }
        } else if (unsure.isNotEmpty()) {
            recognitionStatusText.text = "疑似检测到 ${unsure.size} 人（不确定）"
        } else if (tracking > 0) {
            recognitionStatusText.text = if (noMatch > 0) {
                "跟踪到 $total 人，识别中 $tracking 人，$noMatch 人未匹配"
            } else {
                "跟踪到 $total 人，识别中 $tracking 人"
            }
        } else {
            recognitionStatusText.text = "检测到 $total 张未入库人脸"
        }
    }

    private fun enterUploadMode() {
        uploadStatusText.text = "正在准备注册上传服务..."
        startUploadSession()
    }

    private fun startUploadSession() {
        if (currentMode != AppMode.UPLOAD) return

        val server = uploadServer ?: UploadServer(this, repository).also {
            uploadServer = it
        }
        runCatching {
            server.start()
            uploadStatusText.text = "请在电脑浏览器打开以下地址"
            uploadUrlText.text = server.accessUrl()
        }.onFailure {
            val message = it.message ?: "unknown error"
            uploadStatusText.text = "上传服务启动失败：$message"
            uploadUrlText.text = "-"
        }
    }

    private fun stopUploadSession() {
        uploadServer?.close()
        uploadServer = null
        uploadStatusText.text = "注册上传服务已停止"
        uploadUrlText.text = "-"
    }

    private fun ensureProcessor(): FaceProcessor? {
        val current = processor
        if (current != null) return current

        val created = runCatching {
            FaceProcessor(this).also { it.warmUp() }
        }
            .onFailure {
                val message = it.message ?: "unknown error"
                recognitionStatusText.text = "模型初始化失败：$message"
                toast("模型初始化失败：$message")
            }
            .getOrNull()
        processor = created
        return created
    }

    private fun hasCameraPermission(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
    }

    private fun toast(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }

    private fun loadMatcherConfig() {
        euclideanThreshold = matcherPrefs.getFloat(KEY_EUCLIDEAN_THRESHOLD, DEFAULT_EUCLIDEAN_THRESHOLD)
        cosineThreshold = matcherPrefs.getFloat(KEY_COSINE_THRESHOLD, DEFAULT_COSINE_THRESHOLD)
        matcher = FaceMatcher(euclideanThreshold, cosineThreshold)
    }

    private fun saveMatcherConfig() {
        val parsedEuclidean = euclideanThresholdInput.text?.toString()?.trim()?.toFloatOrNull()
        val parsedCosine = cosineThresholdInput.text?.toString()?.trim()?.toFloatOrNull()

        if (parsedEuclidean == null || parsedCosine == null) {
            toast("请输入有效的数字")
            return
        }

        if (parsedEuclidean !in 0f..2f) {
            toast("欧氏阈值建议在 0 到 2 之间")
            return
        }

        if (parsedCosine !in -1f..1f) {
            toast("余弦阈值建议在 -1 到 1 之间")
            return
        }

        euclideanThreshold = parsedEuclidean
        cosineThreshold = parsedCosine
        matcher = FaceMatcher(euclideanThreshold, cosineThreshold)
        matcherPrefs.edit()
            .putFloat(KEY_EUCLIDEAN_THRESHOLD, euclideanThreshold)
            .putFloat(KEY_COSINE_THRESHOLD, cosineThreshold)
            .apply()

        matcherConfigSummaryText.text = matcherConfigSummary()
        recognitionStatusText.text = "识别参数已更新"
        toast("识别参数已保存")
    }

    private fun matcherConfigSummary(): String {
        return "当前参数：欧氏 <= ${formatThreshold(euclideanThreshold)}，余弦 >= ${formatThreshold(cosineThreshold)}"
    }

    private fun formatThreshold(value: Float): String {
        return String.format(java.util.Locale.US, "%.2f", value)
    }

    private fun mapDetectionsToPreview(image: ImageProxy, rawDetections: List<RawDetection>): List<DetectionUi> {
        val previewWidth = previewView.width.toFloat()
        val previewHeight = previewView.height.toFloat()
        if (previewWidth <= 0f || previewHeight <= 0f) {
            return rawDetections.map { DetectionUi(RectF(it.bounds), it.result, it.confidence) }
        }

        val rotatedCropRect = rotateCropRectToDisplaySpace(
            cropRect = image.cropRect,
            imageWidth = image.width,
            imageHeight = image.height,
            rotationDegrees = image.imageInfo.rotationDegrees
        )
        val scale = maxOf(
            previewWidth / rotatedCropRect.width().coerceAtLeast(1),
            previewHeight / rotatedCropRect.height().coerceAtLeast(1)
        )
        val offsetX = (previewWidth - rotatedCropRect.width() * scale) / 2f
        val offsetY = (previewHeight - rotatedCropRect.height() * scale) / 2f

        return rawDetections.map { detection ->
            val rect = RectF(
                (detection.bounds.left - rotatedCropRect.left) * scale + offsetX,
                (detection.bounds.top - rotatedCropRect.top) * scale + offsetY,
                (detection.bounds.right - rotatedCropRect.left) * scale + offsetX,
                (detection.bounds.bottom - rotatedCropRect.top) * scale + offsetY
            )
            DetectionUi(rect, detection.result, detection.confidence)
        }
    }

    private fun rotateCropRectToDisplaySpace(
        cropRect: Rect,
        imageWidth: Int,
        imageHeight: Int,
        rotationDegrees: Int
    ): Rect {
        return when ((rotationDegrees % 360 + 360) % 360) {
            0 -> Rect(cropRect)
            90 -> Rect(
                imageHeight - cropRect.bottom,
                cropRect.left,
                imageHeight - cropRect.top,
                cropRect.right
            )
            180 -> Rect(
                imageWidth - cropRect.right,
                imageHeight - cropRect.bottom,
                imageWidth - cropRect.left,
                imageHeight - cropRect.top
            )
            270 -> Rect(
                cropRect.top,
                imageWidth - cropRect.right,
                cropRect.bottom,
                imageWidth - cropRect.left
            )
            else -> Rect(cropRect)
        }
    }

    private fun stabilizeRecognition(face: Face, result: RecognitionResult): RecognitionResult {
        val trackId = face.trackingId ?: return result
        val history = recognitionHistory.getOrPut(trackId) { ArrayDeque() }
        history.addLast(result)
        while (history.size > 5) {
            history.removeFirst()
        }

        val matched = history.filter { it.isMatched }
        if (matched.isEmpty()) return result

        val grouped = matched.groupBy { it.student?.id }
        val best = grouped.maxByOrNull { entry ->
            entry.value.sumOf { it.confidence.toDouble() }
        } ?: return result
        val bestResults = best.value
        val bestScore = bestResults.sumOf { it.confidence.toDouble() }.toFloat()
        val bestLatest = bestResults.maxByOrNull { it.confidence } ?: return result
        val secondScore = grouped
            .filterKeys { it != best.key }
            .maxOfOrNull { entry -> entry.value.sumOf { it.confidence.toDouble() }.toFloat() }
            ?: 0f

        return when {
            bestResults.size >= 2 -> bestLatest
            bestLatest.confidence >= 0.92f && secondScore == 0f -> bestLatest
            bestScore >= 1.25f && (bestScore - secondScore) >= 0.25f -> bestLatest
            else -> result
        }
    }

    private fun shouldRefreshRecognition(face: Face, now: Long): Boolean {
        val trackId = face.trackingId ?: return true
        val cached = recognitionCache[trackId] ?: return true
        if (now - cached.lastSeenAtMs > TRACK_STALE_MS) return true
        if (hasTrackMovedSignificantly(cached.lastBounds, face.boundingBox)) return true

        val refreshInterval = when {
            cached.result.isMatched && cached.result.confidence >= MATCH_STABLE_CONFIDENCE -> MATCH_REFRESH_MS
            cached.result.matchType == MatchType.UNSURE -> UNSURE_REFRESH_MS
            cached.result.matchType == MatchType.NO_MATCH -> NO_MATCH_REFRESH_MS
            else -> MATCH_REFRESH_MS
        }
        return now - cached.updatedAtMs >= refreshInterval
    }

    private fun hasTrackMovedSignificantly(previousBounds: RectF, currentBounds: Rect): Boolean {
        val current = RectF(currentBounds)
        val widthBase = maxOf(previousBounds.width(), current.width(), 1f)
        val heightBase = maxOf(previousBounds.height(), current.height(), 1f)
        val centerDx = kotlin.math.abs(previousBounds.centerX() - current.centerX()) / widthBase
        val centerDy = kotlin.math.abs(previousBounds.centerY() - current.centerY()) / heightBase
        val sizeDiff = kotlin.math.abs(previousBounds.width() - current.width()) / widthBase
        val heightDiff = kotlin.math.abs(previousBounds.height() - current.height()) / heightBase
        return centerDx > TRACK_MOVE_THRESHOLD || centerDy > TRACK_MOVE_THRESHOLD || sizeDiff > TRACK_SIZE_THRESHOLD || heightDiff > TRACK_SIZE_THRESHOLD
    }

    private fun pruneTrackCache(now: Long, activeTrackIds: Set<Int>) {
        recognitionCache.entries.removeAll { (trackId, state) ->
            trackId !in activeTrackIds && now - state.lastSeenAtMs > TRACK_STALE_MS
        }
        recognitionHistory.entries.removeAll { (trackId, _) ->
            trackId !in recognitionCache
        }
    }

    private fun refreshRecognition(
        faceProcessor: FaceProcessor,
        bitmap: Bitmap,
        face: Face
    ): RecognitionResult {
        val primary = matcher.findNearest(faceProcessor.embedFace(bitmap, face), students)
        val cached = face.trackingId?.let { recognitionCache[it] }
        val needsEscalation = cached == null ||
            !cached.result.isMatched ||
            primary.matchType != MatchType.MATCH ||
            primary.confidence < cached.result.confidence - 0.05f ||
            hasTrackMovedSignificantly(cached.lastBounds, face.boundingBox)

        if (!needsEscalation) {
            return primary
        }

        var best = primary
        for (embedding in faceProcessor.embedFaceCandidates(bitmap, face)) {
            val result = matcher.findNearest(embedding, students)
            if (recognitionScore(result) > recognitionScore(best)) {
                best = result
            }
        }
        return best
    }

    private fun resolveRecognitionResult(
        face: Face,
        refreshedResult: RecognitionResult?,
        now: Long
    ): RecognitionResult {
        val trackId = face.trackingId
        val previousState = trackId?.let { recognitionCache[it] }
        val previousResult = previousState?.result
        val directResult = when {
            refreshedResult != null -> refreshedResult
            previousResult != null -> previousResult
            else -> RecognitionResult(null, Float.MAX_VALUE, 0f, MatchType.NO_DATA)
        }
        val stabilized = stabilizeRecognition(face, directResult)
        val finalResult = when {
            previousResult?.isMatched == true && !stabilized.isMatched -> previousResult
            previousResult?.isMatched == true &&
                stabilized.isMatched &&
                previousResult.student?.id != stabilized.student?.id &&
                stabilized.confidence < previousResult.confidence + 0.12f -> previousResult
            previousResult?.isMatched == true &&
                stabilized.isMatched &&
                previousResult.student?.id == stabilized.student?.id &&
                stabilized.confidence + 0.08f < previousResult.confidence -> previousResult
            else -> stabilized
        }
        if (trackId != null) {
            val stableFrames = when {
                previousState?.result?.student?.id == finalResult.student?.id && finalResult.isMatched ->
                    (previousState?.stableFrames ?: 0) + 1
                finalResult.isMatched -> 1
                else -> 0
            }
            recognitionCache[trackId] = CachedRecognition(
                result = finalResult,
                updatedAtMs = if (refreshedResult != null) now else previousState?.updatedAtMs ?: now,
                lastSeenAtMs = now,
                lastBounds = RectF(face.boundingBox),
                stableFrames = stableFrames
            )
        }
        return finalResult
    }

    private fun recognitionScore(result: RecognitionResult): Float {
        return matchRank(result.matchType) * 10f + result.confidence + result.cosineSimilarity * 0.1f
    }

    private fun matchRank(matchType: MatchType): Int {
        return when (matchType) {
            MatchType.MATCH -> 3
            MatchType.UNSURE -> 2
            MatchType.NO_MATCH -> 1
            MatchType.NO_DATA -> 0
        }
    }

    companion object {
        private const val MATCHER_PREFS_NAME = "face_matcher_config"
        private const val KEY_EUCLIDEAN_THRESHOLD = "euclidean_threshold"
        private const val KEY_COSINE_THRESHOLD = "cosine_threshold"
        private const val DEFAULT_EUCLIDEAN_THRESHOLD = 1.0f
        private const val DEFAULT_COSINE_THRESHOLD = 0.6f
        private const val MATCH_REFRESH_MS = 900L
        private const val UNSURE_REFRESH_MS = 260L
        private const val NO_MATCH_REFRESH_MS = 160L
        private const val TRACK_STALE_MS = 900L
        private const val TRACK_MOVE_THRESHOLD = 0.16f
        private const val TRACK_SIZE_THRESHOLD = 0.20f
        private const val MATCH_STABLE_CONFIDENCE = 0.82f
        private const val ANALYSIS_INTERVAL_MS = 80L
        private const val DETECTION_GRACE_MS = 700L
        private val ANALYSIS_SIZE = Size(640, 480)
    }
}

private enum class AppMode {
    HOME,
    RECOGNITION,
    UPLOAD
}

private data class RawDetection(
    val bounds: Rect,
    val result: RecognitionResult,
    val confidence: Float?
)

private data class CachedRecognition(
    val result: RecognitionResult,
    val updatedAtMs: Long,
    val lastSeenAtMs: Long,
    val lastBounds: RectF,
    val stableFrames: Int
)
