package com.photobooth.app

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionManager
import androidx.constraintlayout.widget.ConstraintLayout
import com.photobooth.app.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import android.os.Build
import androidx.camera.core.ImageCaptureException
import com.google.mediapipe.tasks.core.BaseOptions
import com.google.mediapipe.tasks.vision.imagesegmenter.ImageSegmenter
import com.google.mediapipe.tasks.vision.imagesegmenter.ImageSegmenter.ImageSegmenterOptions
import com.google.mediapipe.framework.image.BitmapImageBuilder
import com.google.mediapipe.framework.image.ByteBufferExtractor
import java.nio.ByteBuffer

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var cameraExecutor: ExecutorService

    // Camera settings state
    private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA // Default front camera
    private var flashMode = ImageCapture.FLASH_MODE_OFF          // Default flash off
    private var timerSeconds = 0                                 // Default no timer (0s)
    private var cameraControl: CameraControl? = null             // Controls zoom/flash
    private var cameraInfo: CameraInfo? = null                   // Query capabilities
    private var imageCapture: ImageCapture? = null               // Capture usecase
    private var isCountingDown = false                           // Countdown state flag

    // Frame & Preset UI selectors state
    private lateinit var allFrames: List<FrameItem>
    private lateinit var colorPresets: List<ColorPreset>
    private lateinit var frameItemAdapter: FrameItemAdapter
    private lateinit var presetAdapter: PresetAdapter

    private var isFrameModeOpen = false
    private var isPresetModeOpen = false
    private var isPortraitBlurModeOpen = false
    private var isBgReplaceModeOpen = false
    private var currentSelectedFramePath: String? = null
    private var currentSelectedPreset: ColorPreset? = null
    private var editingPreset: ColorPreset? = null

    private var currentAttribute: EditAttribute = EditAttribute.BRIGHTNESS
    private lateinit var attributeAdapter: AttributeAdapter
    private var startX = 0f
    private var startVal = 0

    // Background replacement and Portrait blur state
    private var blurIntensity = 0.0f
    private var selectedBgAssetPath: String? = null
    private lateinit var backgroundOptionAdapter: BackgroundOptionAdapter
    private lateinit var backgroundOptions: List<BackgroundOption>
    private val aiInferenceLock = Any()
    private var imageSegmenter: ImageSegmenter? = null
    private var isAiSupported = true

    // Touch-to-Focus: ID lớp đối tượng được chạm chọn lấy nét (-1 = mặc định lấy nét người ID 1)
    private var userSelectedClassFocus = -1
    // Cache mặt nạ phân lớp mới nhất từ MediaPipe để phục vụ Touch-to-Focus
    private var cachedCategoryMask: ByteArray? = null
    private var cachedMaskWidth = 0
    private var cachedMaskHeight = 0

    private fun getImageSegmenter(): ImageSegmenter? {
        synchronized(aiInferenceLock) {
            if (!isAiSupported) return null
            if (imageSegmenter == null) {
                try {
                    val baseOptions = BaseOptions.builder()
                        .setModelAssetPath("models/multiclass_segmenter.tflite")
                        .build()

                    val options = ImageSegmenterOptions.builder()
                        .setBaseOptions(baseOptions)
                        .setOutputCategoryMask(true)
                        .setOutputConfidenceMasks(false)
                        .build()

                    imageSegmenter = ImageSegmenter.createFromOptions(this, options)
                } catch (e: UnsatisfiedLinkError) {
                    isAiSupported = false
                    runOnUiThread {
                        Toast.makeText(this, "Không hỗ trợ xử lý AI trên thiết bị/emulator này (thiếu thư viện Native JNI)", Toast.LENGTH_LONG).show()
                    }
                    return null
                } catch (e: Exception) {
                    isAiSupported = false
                    runOnUiThread {
                        Toast.makeText(this, "Lỗi khởi tạo AI Segmenter: ${e.message}", Toast.LENGTH_LONG).show()
                    }
                    return null
                }
            }
            return imageSegmenter
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Setup gallery thumbnail outline clipping for fly-to-thumbnail effect
        binding.btnGalleryReview.clipToOutline = true
        binding.btnGalleryReview.background = ContextCompat.getDrawable(this, R.drawable.bg_gallery_thumbnail)
        binding.btnGalleryReview.scaleType = android.widget.ImageView.ScaleType.CENTER_CROP

        // Setup UI control listeners
        setupCameraControls()

        // Setup Frame/Preset selection lists
        initFrameData()
        setupRecyclerViews()

        // Request camera permission
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        cameraExecutor = Executors.newSingleThreadExecutor()
    }

    private fun initFrameData() {
        allFrames = listOf(
            FrameItem("DEFAULT", "Mặc định", ""),
            FrameItem("instax_mini_single", "Instax Mini", "frames/instaxframe.png")
        )

        // Initialize default Color Presets
        val defaultPresets = listOf(
            ColorPreset(
                id = "DEFAULT",
                name = "Mặc định",
                themeCategory = "None",
                isEditable = false,
                basic = BasicAdjustments(0f, 0f, 0f, 0f, 0f, 0f),
                advanced = AdvancedEffects(0f, 0f, 0f),
                toneCurve = ToneCurveEmulation(0f, 0f, 0f, 0f),
                grain = FilmGrain(0f, 0f)
            ),
            ColorPreset(
                id = "instax_faded_warm_01",
                name = "Instax Nắng Chiều",
                themeCategory = "Retro Vintage",
                isEditable = true,
                basic = BasicAdjustments(0.05f, -0.15f, -0.20f, 0.25f, 0.18f, -0.02f),
                advanced = AdvancedEffects(-0.20f, -0.15f, 0.35f),
                toneCurve = ToneCurveEmulation(0.12f, 0.0f, 0.04f, 0.08f),
                grain = FilmGrain(0.28f, 0.15f)
            ),
            ColorPreset(
                id = "instax_bw_cool",
                name = "Instax Trắng Đen",
                themeCategory = "Monochrome",
                isEditable = true,
                basic = BasicAdjustments(0.0f, 0.10f, -1.0f, 0.0f, -0.05f, 0.0f),
                advanced = AdvancedEffects(0.05f, 0.0f, 0.20f),
                toneCurve = ToneCurveEmulation(0.08f, 0.0f, 0.0f, 0.0f),
                grain = FilmGrain(0.35f, 0.18f)
            ),
            ColorPreset(
                id = "instax_cool_summer",
                name = "Mùa Hè Dịu Mát",
                themeCategory = "Summer",
                isEditable = true,
                basic = BasicAdjustments(0.02f, -0.05f, -0.10f, 0.15f, -0.18f, 0.05f),
                advanced = AdvancedEffects(-0.10f, -0.05f, 0.15f),
                toneCurve = ToneCurveEmulation(0.05f, 0.02f, 0.05f, 0.10f),
                grain = FilmGrain(0.15f, 0.12f)
            )
        )

        // Load custom user saved presets from filesDir
        colorPresets = defaultPresets + loadCustomPresets()
    }

    private fun loadCustomPresets(): List<ColorPreset> {
        val customList = mutableListOf<ColorPreset>()
        val files = filesDir.listFiles { _, name -> name.endsWith(".json") }
        files?.forEach { file ->
            try {
                val content = file.readText()
                val id = extractJsonField(content, "id")
                val name = extractJsonField(content, "name")
                val category = extractJsonField(content, "theme_category")
                
                val brightness = extractJsonDoubleField(content, "brightness").toFloat()
                val contrast = extractJsonDoubleField(content, "contrast").toFloat()
                val saturation = extractJsonDoubleField(content, "saturation").toFloat()
                val vibrance = extractJsonDoubleField(content, "vibrance").toFloat()
                val temperature = extractJsonDoubleField(content, "temperature").toFloat()
                val tint = extractJsonDoubleField(content, "tint").toFloat()

                val clarity = extractJsonDoubleField(content, "clarity").toFloat()
                val dehaze = extractJsonDoubleField(content, "dehaze").toFloat()
                val vignetteAmount = extractJsonDoubleField(content, "vignette_amount").toFloat()

                val fadedBlackLevel = extractJsonDoubleField(content, "faded_black_level").toFloat()
                val shadowsTintR = extractJsonDoubleField(content, "shadows_tint_r").toFloat()
                val shadowsTintG = extractJsonDoubleField(content, "shadows_tint_g").toFloat()
                val shadowsTintB = extractJsonDoubleField(content, "shadows_tint_b").toFloat()

                val grainAmount = extractJsonDoubleField(content, "grain_amount").toFloat()
                val grainSize = extractJsonDoubleField(content, "grain_size").toFloat()

                val preset = ColorPreset(
                    id = id,
                    name = name,
                    themeCategory = category,
                    isEditable = true,
                    basic = BasicAdjustments(brightness, contrast, saturation, vibrance, temperature, tint),
                    advanced = AdvancedEffects(clarity, dehaze, vignetteAmount),
                    toneCurve = ToneCurveEmulation(fadedBlackLevel, shadowsTintR, shadowsTintG, shadowsTintB),
                    grain = FilmGrain(grainAmount, grainSize)
                )
                customList.add(preset)
            } catch (e: Exception) {
                // Skip invalid JSON presets
            }
        }
        return customList
    }

    private fun extractJsonField(json: String, field: String): String {
        val pattern = "\"$field\"\\s*:\\s*\"([^\"]*)\"".toRegex()
        return pattern.find(json)?.groupValues?.get(1) ?: ""
    }

    private fun extractJsonDoubleField(json: String, field: String): Double {
        val pattern = "\"$field\"\\s*:\\s*(-?\\d+\\.?\\d*)".toRegex()
        return pattern.find(json)?.groupValues?.get(1)?.toDoubleOrNull() ?: 0.0
    }

    private fun setupRecyclerViews() {
        // Setup Shared Timeline RecyclerView
        binding.rvHorizontalTimeline.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        
        frameItemAdapter = FrameItemAdapter(allFrames) { frame ->
            setFrameOverlay(frame.imageFileName)
            updateFrameModeIcon(frame)
        }

        presetAdapter = PresetAdapter(colorPresets, { preset ->
            applyPresetFilter(preset)
            updatePresetModeIcon(preset)
        }, { preset ->
            openAdvancedSliders(preset)
        })

        // Setup Attributes List and Ruler View for the editor panel
        val attributeItems = listOf(
            AttributeNode(EditAttribute.BRIGHTNESS, "Brightness (Độ sáng)", R.drawable.ic_attr_brightness),
            AttributeNode(EditAttribute.CONTRAST, "Contrast (Tương phản)", R.drawable.ic_attr_contrast),
            AttributeNode(EditAttribute.SATURATION, "Saturation (Bão hòa màu)", R.drawable.ic_attr_saturation),
            AttributeNode(EditAttribute.VIBRANCE, "Vibrance (Bão hòa thông minh)", R.drawable.ic_attr_vibrance),
            AttributeNode(EditAttribute.TEMPERATURE, "Temperature (Nhiệt màu)", R.drawable.ic_attr_temperature),
            AttributeNode(EditAttribute.TINT, "Tint (Sắc độ hồng/xanh)", R.drawable.ic_attr_tint),
            AttributeNode(EditAttribute.CLARITY, "Clarity (Độ rõ nét)", R.drawable.ic_attr_clarity),
            AttributeNode(EditAttribute.DEHAZE, "Dehaze (Độ trong suốt / Sương mù)", R.drawable.ic_attr_dehaze),
            AttributeNode(EditAttribute.VIGNETTE_AMOUNT, "Vignette Amount (Bo tối góc)", R.drawable.ic_attr_vignette),
            AttributeNode(EditAttribute.FADED_BLACK_LEVEL, "Faded Black Level (Độ mờ vùng đen)", R.drawable.ic_attr_fade),
            AttributeNode(EditAttribute.SHADOWS_TINT_R, "Shadows Tint R (Sắc Đỏ vùng tối)", R.drawable.ic_attr_tint_r),
            AttributeNode(EditAttribute.SHADOWS_TINT_G, "Shadows Tint G (Sắc Lục vùng tối)", R.drawable.ic_attr_tint_g),
            AttributeNode(EditAttribute.SHADOWS_TINT_B, "Shadows Tint B (Sắc Lam vùng tối)", R.drawable.ic_attr_tint_b),
            AttributeNode(EditAttribute.GRAIN_AMOUNT, "Grain Amount (Mật độ hạt)", R.drawable.ic_attr_grain_amount),
            AttributeNode(EditAttribute.GRAIN_SIZE, "Grain Size (Kích thước hạt)", R.drawable.ic_attr_grain_size),
            AttributeNode(EditAttribute.PORTRAIT_BLUR, "Portrait Blur (Xóa phông)", R.drawable.ic_attr_portrait_blur),
            AttributeNode(EditAttribute.BG_REPLACE, "BG Replace (Thay nền)", R.drawable.ic_attr_bg_replace)
        )
        attributeAdapter = AttributeAdapter(attributeItems) { node ->
            onAttributeSelected(node)
        }
        binding.rvAttributeIcons.layoutManager = LinearLayoutManager(
            this, LinearLayoutManager.HORIZONTAL, false
        )
        binding.rvAttributeIcons.adapter = attributeAdapter

        // Initialize Background replacement options list
        backgroundOptions = listOf(
            BackgroundOption("NONE", "Mặc định", null),
            BackgroundOption("BG_01", "Nền 1", "backgrounds/background-01.png"),
            BackgroundOption("BG_02", "Nền 2", "backgrounds/background-02.png"),
            BackgroundOption("BG_03", "Nền 3", "backgrounds/background-03.png")
        )
        backgroundOptionAdapter = BackgroundOptionAdapter(backgroundOptions) { option ->
            selectedBgAssetPath = option.assetPath
        }
        binding.rvBackgroundOptions.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        binding.rvBackgroundOptions.adapter = backgroundOptionAdapter

        binding.rulerView.onValueChangeListener = { valInt ->
            binding.tvRulerValue.text = "$valInt"
            setCurrentAttributeValue(valInt / 100f)
            editingPreset?.let { applyPresetFilter(it) }
        }

        binding.panelRulerView.minVal = 0
        binding.panelRulerView.maxVal = 100
        binding.panelRulerView.onValueChangeListener = { valInt ->
            binding.tvPanelRulerValue.text = "$valInt"
            blurIntensity = valInt / 100f
        }
    }

    private fun setFrameOverlay(fileName: String) {
        if (fileName.isEmpty()) {
            binding.imgFrameOverlay.setImageDrawable(null)
            currentSelectedFramePath = null
        } else {
            try {
                val inputStream = assets.open(fileName)
                val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream)
                binding.imgFrameOverlay.setImageBitmap(bitmap)
                currentSelectedFramePath = fileName
            } catch (e: Exception) {
                val errorMsg = getString(R.string.toast_frame_load_error, fileName)
                Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
            }
        }
        adjustViewFinderBounds()
    }

    private fun adjustViewFinderBounds() {
        val framePath = currentSelectedFramePath
        if (framePath == null || framePath.isEmpty()) {
            // Reset viewFinder to match_parent
            val params = binding.viewFinder.layoutParams as android.widget.FrameLayout.LayoutParams
            params.width = android.widget.FrameLayout.LayoutParams.MATCH_PARENT
            params.height = android.widget.FrameLayout.LayoutParams.MATCH_PARENT
            params.topMargin = 0
            params.bottomMargin = 0
            params.leftMargin = 0
            params.rightMargin = 0
            binding.viewFinder.layoutParams = params
            
            // Reset AI preview overlay
            val aiParams = binding.imgAiPreviewOverlay.layoutParams as android.widget.FrameLayout.LayoutParams
            aiParams.width = android.widget.FrameLayout.LayoutParams.MATCH_PARENT
            aiParams.height = android.widget.FrameLayout.LayoutParams.MATCH_PARENT
            aiParams.topMargin = 0
            aiParams.bottomMargin = 0
            aiParams.leftMargin = 0
            aiParams.rightMargin = 0
            binding.imgAiPreviewOverlay.layoutParams = aiParams

            // Also reset filter overlay
            val filterParams = binding.imgFilterOverlay.layoutParams as android.widget.FrameLayout.LayoutParams
            filterParams.width = android.widget.FrameLayout.LayoutParams.MATCH_PARENT
            filterParams.height = android.widget.FrameLayout.LayoutParams.MATCH_PARENT
            filterParams.topMargin = 0
            filterParams.bottomMargin = 0
            filterParams.leftMargin = 0
            filterParams.rightMargin = 0
            binding.imgFilterOverlay.layoutParams = filterParams
        } else {
            binding.cameraContainer.post {
                val containerWidth = binding.cameraContainer.width
                val containerHeight = binding.cameraContainer.height
                if (containerWidth > 0 && containerHeight > 0) {
                    // We know instaxframe is 1024 x 1207
                    val imgW = 1024f
                    val imgH = 1207f
                    val aspect = imgW / imgH
                    
                    val containerAspect = containerWidth.toFloat() / containerHeight.toFloat()
                    
                    var targetWidth = containerWidth
                    var targetHeight = containerHeight
                    
                    if (containerAspect > aspect) {
                        // Container is wider than the image aspect -> height is matched, width is scaled
                        targetWidth = (containerHeight * aspect).toInt()
                    } else {
                        // Container is taller than the image aspect -> width is matched, height is scaled
                        targetHeight = (containerWidth / aspect).toInt()
                    }
                    
                    val verticalMargin = (containerHeight - targetHeight) / 2
                    val horizontalMargin = (containerWidth - targetWidth) / 2
                    
                    // Adjust viewFinder params
                    val params = binding.viewFinder.layoutParams as android.widget.FrameLayout.LayoutParams
                    params.width = targetWidth
                    params.height = targetHeight
                    params.topMargin = verticalMargin
                    params.bottomMargin = verticalMargin
                    params.leftMargin = horizontalMargin
                    params.rightMargin = horizontalMargin
                    binding.viewFinder.layoutParams = params

                    // Adjust AI preview overlay params
                    val aiParams = binding.imgAiPreviewOverlay.layoutParams as android.widget.FrameLayout.LayoutParams
                    aiParams.width = targetWidth
                    aiParams.height = targetHeight
                    aiParams.topMargin = verticalMargin
                    aiParams.bottomMargin = verticalMargin
                    aiParams.leftMargin = horizontalMargin
                    aiParams.rightMargin = horizontalMargin
                    binding.imgAiPreviewOverlay.layoutParams = aiParams
                    
                    // Adjust filter overlay params
                    val filterParams = binding.imgFilterOverlay.layoutParams as android.widget.FrameLayout.LayoutParams
                    filterParams.width = targetWidth
                    filterParams.height = targetHeight
                    filterParams.topMargin = verticalMargin
                    filterParams.bottomMargin = verticalMargin
                    filterParams.leftMargin = horizontalMargin
                    filterParams.rightMargin = horizontalMargin
                    binding.imgFilterOverlay.layoutParams = filterParams
                }
            }
        }
    }

    private fun getColorMatrixForPreset(preset: ColorPreset): android.graphics.ColorMatrix {
        val finalMatrix = android.graphics.ColorMatrix()

        // 1. Saturation & Vibrance (Sắc độ bão hòa màu thực tế)
        val sat = (preset.basic.saturation + preset.basic.vibrance * 0.5f) + 1f
        val satMatrix = android.graphics.ColorMatrix().apply {
            setSaturation(sat.coerceIn(0f, 3f))
        }
        finalMatrix.postConcat(satMatrix)

        // 2. Contrast (Tương phản thực tế)
        val scale = preset.basic.contrast + 1f
        val translate = (-0.5f * scale + 0.5f) * 255f
        val contrastMatrix = android.graphics.ColorMatrix(floatArrayOf(
            scale, 0f, 0f, 0f, translate,
            0f, scale, 0f, 0f, translate,
            0f, 0f, scale, 0f, translate,
            0f, 0f, 0f, 1f, 0f
        ))
        finalMatrix.postConcat(contrastMatrix)

        // 3. Brightness (Độ sáng Exposure thực tế)
        val brightnessOffset = preset.basic.brightness * 60f
        val brightnessMatrix = android.graphics.ColorMatrix(floatArrayOf(
            1f, 0f, 0f, 0f, brightnessOffset,
            0f, 1f, 0f, 0f, brightnessOffset,
            0f, 0f, 1f, 0f, brightnessOffset,
            0f, 0f, 0f, 1f, 0f
        ))
        finalMatrix.postConcat(brightnessMatrix)

        // 4. Temperature (Nhiệt màu thực tế: Vàng ấm / Xanh lạnh)
        val temp = preset.basic.temperature
        val rScale = 1f + temp * 0.12f
        val gScale = 1f + temp * 0.04f
        val bScale = 1f - temp * 0.12f
        val tempMatrix = android.graphics.ColorMatrix(floatArrayOf(
            rScale, 0f, 0f, 0f, 0f,
            0f, gScale, 0f, 0f, 0f,
            0f, 0f, bScale, 0f, 0f,
            0f, 0f, 0f, 1f, 0f
        ))
        finalMatrix.postConcat(tempMatrix)

        // 5. Tint (Sắc độ: Hồng dâu / Xanh lá)
        val tint = preset.basic.tint
        val rScaleTint = 1f + tint * 0.06f
        val gScaleTint = 1f - tint * 0.12f
        val bScaleTint = 1f + tint * 0.06f
        val tintMatrix = android.graphics.ColorMatrix(floatArrayOf(
            rScaleTint, 0f, 0f, 0f, 0f,
            0f, gScaleTint, 0f, 0f, 0f,
            0f, 0f, bScaleTint, 0f, 0f,
            0f, 0f, 0f, 1f, 0f
        ))
        finalMatrix.postConcat(tintMatrix)

        return finalMatrix
    }

    private fun getFilterColorForPreset(preset: ColorPreset): Int {
        if (preset.id == "DEFAULT") return android.graphics.Color.TRANSPARENT
        val isBeingEdited = editingPreset != null
        val useCalculation = isBeingEdited || (preset.id != "instax_faded_warm_01" && preset.id != "instax_bw_cool" && preset.id != "instax_cool_summer")
        return if (useCalculation) {
            // 1. ALPHA (Độ đậm/Mờ của lớp filter): Được quyết định bởi Saturation, Vibrance và hiệu ứng nâng cao
            // Quy đổi dải từ -1.0 -> 1.0 sang mức độ trong suốt hợp lý (từ 15 đến 140 trong hệ 255)
            val baseAlpha = 60f
            val saturationImpact = (preset.basic.saturation + preset.basic.vibrance) * 25f
            val contrastImpact = preset.basic.contrast * 15f
            val alpha = (baseAlpha + saturationImpact + contrastImpact).toInt().coerceIn(15, 140)

            // 2. BASE COLOR (Màu nền mặc định ban đầu trước khi tinh chỉnh)
            // Bắt đầu từ một màu trung tính hoặc lấy màu gốc của Theme nếu có
            var baseR = 140f
            var baseG = 130f
            var baseB = 120f

            // 3. BRIGHTNESS (Độ sáng): Bắt buộc phải cộng/trừ ĐỀU vào cả 3 kênh R, G, B để dịch chuyển Exposure
            val brightnessOffset = preset.basic.brightness * 80f // Nhân hệ số để biên độ thay đổi từ -100 đến +100 rõ rệt
            baseR += brightnessOffset
            baseG += brightnessOffset
            baseB += brightnessOffset

            // 4. TEMPERATURE (Nhiệt màu): + là Ấm (Tăng Đỏ, Giảm Lam), - là Lạnh (Giảm Đỏ, Tăng Lam)
            val tempOffset = preset.basic.temperature * 50f
            baseR += tempOffset
            baseB -= tempOffset

            // 5. TINT (Sắc độ): + là Ám hồng (Tăng Đỏ & Lam, Giảm Lục), - là Ám xanh (Giảm Đỏ & Lam, Tăng Lục)
            val tintOffset = preset.basic.tint * 40f
            baseG -= tintOffset
            baseR += (tintOffset * 0.5f)
            baseB += (tintOffset * 0.5f)

            // 6. TONE CURVE & SHADOWS TINT (Ám màu vùng tối đặc trưng của Instax)
            baseR += (preset.toneCurve.shadowsTintR * 35f)
            baseG += (preset.toneCurve.shadowsTintG * 35f)
            baseB += (preset.toneCurve.shadowsTintB * 35f)
            
            // Nâng đáy màu đen (Faded Black) tạo độ sờn ảnh
            val fadeOffset = preset.toneCurve.fadedBlackLevel * 30f
            baseR += fadeOffset
            baseG += fadeOffset
            baseB += fadeOffset

            // 7. CLARITY & DEHAZE (Điều tiết thêm độ tương phản cục bộ của màu)
            val advancedOffset = (preset.advanced.clarity - preset.advanced.dehaze) * 20f
            baseR += advancedOffset
            baseG += advancedOffset
            baseB += advancedOffset

            // 8. ÉP BIÊN ĐỘ (COERCE) để đảm bảo giá trị RGB luôn nằm trong giới hạn vật lý 0 -> 255
            val r = baseR.toInt().coerceIn(0, 255)
            val g = baseG.toInt().coerceIn(0, 255)
            val b = baseB.toInt().coerceIn(0, 255)

            android.graphics.Color.argb(alpha, r, g, b)
        } else {
            // Khối xử lý cho các mẫu mặc định cố định không sửa đổi
            when (preset.id) {
                "instax_faded_warm_01" -> android.graphics.Color.argb(55, 255, 152, 0)
                "instax_bw_cool" -> android.graphics.Color.argb(80, 128, 128, 128)
                "instax_cool_summer" -> android.graphics.Color.argb(45, 0, 188, 212)
                else -> android.graphics.Color.argb(0, 0, 0, 0)
            }
        }
    }

    private fun applyPresetFilter(preset: ColorPreset) {
        currentSelectedPreset = preset
        if (preset.id == "DEFAULT") {
            binding.imgFilterOverlay.visibility = android.view.View.GONE
            binding.viewFinder.setLayerType(android.view.View.LAYER_TYPE_NONE, null)
        } else {
            binding.imgFilterOverlay.visibility = android.view.View.VISIBLE
            val filterColor = getFilterColorForPreset(preset)
            binding.imgFilterOverlay.setBackgroundColor(filterColor)
            applyColorMatrixToViewFinder(preset)
        }
    }

    private fun applyColorMatrixToViewFinder(preset: ColorPreset) {
        val finalMatrix = getColorMatrixForPreset(preset)
        val paint = android.graphics.Paint().apply {
            colorFilter = android.graphics.ColorMatrixColorFilter(finalMatrix)
        }
        binding.viewFinder.setLayerType(android.view.View.LAYER_TYPE_HARDWARE, paint)
    }

    private fun updateFrameModeIcon(frame: FrameItem) {
        if (frame.id == "DEFAULT" || frame.imageFileName.isEmpty()) {
            binding.btnMainFrame.setImageResource(R.drawable.ic_default_frame_thumbnail)
        } else {
            try {
                val inputStream = assets.open(frame.imageFileName)
                val bitmap = android.graphics.BitmapFactory.decodeStream(inputStream)
                binding.btnMainFrame.setImageBitmap(bitmap)
            } catch (e: Exception) {
                binding.btnMainFrame.setImageResource(R.drawable.ic_default_frame_thumbnail)
            }
        }
    }

    private fun updatePresetModeIcon(preset: ColorPreset) {
        if (preset.id == "DEFAULT") {
            binding.btnMainPreset.setImageResource(R.drawable.ic_default_preset_thumbnail)
        } else {
            val colorHex = when (preset.id) {
                "instax_faded_warm_01" -> "#FFF57C00"
                "instax_bw_cool" -> "#FF808080"
                "instax_cool_summer" -> "#FF00BCD4"
                else -> "#FF9E9E9E"
            }
            val circleDrawable = android.graphics.drawable.GradientDrawable().apply {
                shape = android.graphics.drawable.GradientDrawable.OVAL
                setColor(android.graphics.Color.parseColor(colorHex))
            }
            binding.btnMainPreset.setImageDrawable(circleDrawable)
        }
    }

    private fun setupCameraControls() {
        // Mode Selectors
        binding.btnMainFrame.setOnClickListener {
            toggleFrameMode()
        }

        binding.btnMainPreset.setOnClickListener {
            togglePresetMode()
        }

        binding.btnMainPortraitBlur.setOnClickListener {
            togglePortraitBlurMode()
        }

        binding.btnMainBgReplace.setOnClickListener {
            toggleBgReplaceMode()
        }

        // Top Bar
        binding.btnSwitchCamera.setOnClickListener {
            toggleCamera()
        }
        binding.btnFlash.setOnClickListener {
            toggleFlash()
        }
        binding.btnTimer.setOnClickListener {
            toggleTimer()
        }

        // Zoom Text selectors
        binding.tvZoom05.setOnClickListener {
            applyZoom(0.5f)
        }
        binding.tvZoom1.setOnClickListener {
            applyZoom(1.0f)
        }
        binding.tvZoom3.setOnClickListener {
            applyZoom(3.0f)
        }

        // Play Gallery Button
        binding.btnGalleryReview.setOnClickListener {
            try {
                val intent = android.content.Intent(android.content.Intent.ACTION_VIEW).apply {
                    type = "image/*"
                    flags = android.content.Intent.FLAG_ACTIVITY_NEW_TASK
                }
                startActivity(intent)
            } catch (e: Exception) {
                Toast.makeText(this, "Không thể mở Thư viện ảnh", Toast.LENGTH_SHORT).show()
            }
        }

        // Shutter Button
        binding.btnShutterCapture.setOnClickListener {
            onCaptureClick()
        }

        // Camera viewfinder container swipe gesture listener to adjust parameters
        binding.cameraContainer.setOnTouchListener { _, event ->
            val isRulerVisible = binding.layoutTimelineEditor.visibility == android.view.View.VISIBLE
            val isBlurVisible = isPortraitBlurModeOpen
            
            if (!isRulerVisible && !isBlurVisible) {
                return@setOnTouchListener false
            }
            
            when (event.action) {
                android.view.MotionEvent.ACTION_DOWN -> {
                    startX = event.x
                    startVal = if (isBlurVisible) {
                        binding.panelRulerView.value
                    } else {
                        binding.rulerView.value
                    }
                }
                android.view.MotionEvent.ACTION_MOVE -> {
                    val deltaX = event.x - startX
                    val sensitivity = 5f // pixels per unit value change
                    val deltaVal = (deltaX / sensitivity).toInt()
                    
                    if (isBlurVisible) {
                        binding.panelRulerView.value = startVal + deltaVal
                    } else {
                        binding.rulerView.value = startVal + deltaVal
                    }
                }
            }
            true
        }

        binding.btnEditorClose.setOnClickListener {
            closeAdvancedSliders()
        }

        binding.btnEditorCheck.setOnClickListener {
            editingPreset?.let { preset ->
                showSavePresetDialog(preset)
            }
        }

        // Touch-to-Focus: Chạm vào kính ngắm để chọn điểm lấy nét
        binding.viewFinder.setOnTouchListener { v, event ->
            if (event.action == android.view.MotionEvent.ACTION_UP) {
                val percentX = (event.x / v.width).coerceIn(0f, 1f)
                val percentY = (event.y / v.height).coerceIn(0f, 1f)

                // Cập nhật Class ID lấy nét từ điểm chạm
                val mask = cachedCategoryMask
                val mW = cachedMaskWidth
                val mH = cachedMaskHeight
                if (mask != null && mW > 0 && mH > 0) {
                    val maskX = (percentX * mW).toInt().coerceIn(0, mW - 1)
                    val maskY = (percentY * mH).toInt().coerceIn(0, mH - 1)
                    userSelectedClassFocus = mask[maskY * mW + maskX].toInt()
                }

                // Hiện vòng tròn lấy nét màu cam tại vị trí chạm
                val focusRing = binding.viewFocusRing
                focusRing.x = event.x - focusRing.width / 2f
                focusRing.y = event.y - focusRing.height / 2f
                focusRing.visibility = android.view.View.VISIBLE
                focusRing.alpha = 1f
                focusRing.scaleX = 1.4f
                focusRing.scaleY = 1.4f
                focusRing.animate()
                    .scaleX(1f).scaleY(1f)
                    .alpha(0f)
                    .setDuration(900)
                    .withEndAction { focusRing.visibility = android.view.View.GONE }
                    .start()

                v.performClick()
            }
            true
        }
    }

    private fun translateButtonToDock(button: android.view.View) {
        val density = resources.displayMetrics.density
        val params = button.layoutParams as ConstraintLayout.LayoutParams
        params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
        params.endToStart = ConstraintLayout.LayoutParams.UNSET
        params.endToEnd = ConstraintLayout.LayoutParams.UNSET
        params.leftMargin = (12.5f * density).toInt()
        button.layoutParams = params
    }

    private fun restoreMainMenuButtons() {
        val density = resources.displayMetrics.density
        
        // 1. Restore btnMainFrame
        val paramsFrame = binding.btnMainFrame.layoutParams as ConstraintLayout.LayoutParams
        paramsFrame.startToStart = ConstraintLayout.LayoutParams.PARENT_ID
        paramsFrame.endToStart = binding.btnMainPreset.id
        paramsFrame.endToEnd = ConstraintLayout.LayoutParams.UNSET
        paramsFrame.leftMargin = 0
        paramsFrame.rightMargin = (12f * density).toInt()
        binding.btnMainFrame.layoutParams = paramsFrame
        binding.btnMainFrame.visibility = android.view.View.VISIBLE

        // 2. Restore btnMainPreset
        val paramsPreset = binding.btnMainPreset.layoutParams as ConstraintLayout.LayoutParams
        paramsPreset.startToStart = ConstraintLayout.LayoutParams.UNSET
        paramsPreset.startToEnd = binding.btnMainFrame.id
        paramsPreset.endToStart = binding.btnMainPortraitBlur.id
        paramsPreset.endToEnd = ConstraintLayout.LayoutParams.UNSET
        paramsPreset.leftMargin = 0
        paramsPreset.rightMargin = (12f * density).toInt()
        binding.btnMainPreset.layoutParams = paramsPreset
        binding.btnMainPreset.visibility = android.view.View.VISIBLE

        // 3. Restore btnMainPortraitBlur
        val paramsPortrait = binding.btnMainPortraitBlur.layoutParams as ConstraintLayout.LayoutParams
        paramsPortrait.startToStart = ConstraintLayout.LayoutParams.UNSET
        paramsPortrait.startToEnd = binding.btnMainPreset.id
        paramsPortrait.endToStart = binding.btnMainBgReplace.id
        paramsPortrait.endToEnd = ConstraintLayout.LayoutParams.UNSET
        paramsPortrait.leftMargin = 0
        paramsPortrait.rightMargin = (12f * density).toInt()
        binding.btnMainPortraitBlur.layoutParams = paramsPortrait
        binding.btnMainPortraitBlur.visibility = android.view.View.VISIBLE

        // 4. Restore btnMainBgReplace
        val paramsBg = binding.btnMainBgReplace.layoutParams as ConstraintLayout.LayoutParams
        paramsBg.startToStart = ConstraintLayout.LayoutParams.UNSET
        paramsBg.startToEnd = binding.btnMainPortraitBlur.id
        paramsBg.endToStart = ConstraintLayout.LayoutParams.UNSET
        paramsBg.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID
        paramsBg.leftMargin = 0
        paramsBg.rightMargin = 0
        binding.btnMainBgReplace.layoutParams = paramsBg
        binding.btnMainBgReplace.visibility = android.view.View.VISIBLE
    }

    private fun toggleFrameMode() {
        if (isCountingDown) return
        val wrapper = binding.buttonsWrapper
        TransitionManager.beginDelayedTransition(wrapper)

        if (!isFrameModeOpen) {
            isFrameModeOpen = true
            isPresetModeOpen = false
            isPortraitBlurModeOpen = false
            isBgReplaceModeOpen = false

            // Hide other buttons
            binding.btnMainPreset.visibility = android.view.View.GONE
            binding.btnMainPortraitBlur.visibility = android.view.View.GONE
            binding.btnMainBgReplace.visibility = android.view.View.GONE

            translateButtonToDock(binding.btnMainFrame)

            // Show horizontal timeline & solid dock background
            binding.viewLeftDockBackground.visibility = android.view.View.VISIBLE
            binding.rvHorizontalTimeline.visibility = android.view.View.VISIBLE
            binding.rvBackgroundOptions.visibility = android.view.View.GONE
            binding.panelRulerContainer.visibility = android.view.View.GONE
            binding.rvHorizontalTimeline.adapter = frameItemAdapter
        } else {
            isFrameModeOpen = false
            restoreMainMenuButtons()
            binding.viewLeftDockBackground.visibility = android.view.View.GONE
            binding.rvHorizontalTimeline.visibility = android.view.View.GONE
        }
    }

    private fun togglePresetMode() {
        if (isCountingDown) return
        val wrapper = binding.buttonsWrapper
        TransitionManager.beginDelayedTransition(wrapper)

        if (!isPresetModeOpen) {
            isPresetModeOpen = true
            isFrameModeOpen = false
            isPortraitBlurModeOpen = false
            isBgReplaceModeOpen = false

            // Hide other buttons
            binding.btnMainFrame.visibility = android.view.View.GONE
            binding.btnMainPortraitBlur.visibility = android.view.View.GONE
            binding.btnMainBgReplace.visibility = android.view.View.GONE

            translateButtonToDock(binding.btnMainPreset)

            // Show horizontal timeline & solid dock background
            binding.viewLeftDockBackground.visibility = android.view.View.VISIBLE
            binding.rvHorizontalTimeline.visibility = android.view.View.VISIBLE
            binding.rvBackgroundOptions.visibility = android.view.View.GONE
            binding.panelRulerContainer.visibility = android.view.View.GONE
            presetAdapter.resetSelection()
            binding.rvHorizontalTimeline.adapter = presetAdapter
        } else {
            isPresetModeOpen = false
            restoreMainMenuButtons()
            binding.viewLeftDockBackground.visibility = android.view.View.GONE
            binding.rvHorizontalTimeline.visibility = android.view.View.GONE
        }
    }

    private fun togglePortraitBlurMode() {
        if (isCountingDown) return
        val wrapper = binding.buttonsWrapper
        TransitionManager.beginDelayedTransition(wrapper)

        if (!isPortraitBlurModeOpen) {
            isPortraitBlurModeOpen = true
            isFrameModeOpen = false
            isPresetModeOpen = false
            isBgReplaceModeOpen = false

            // Hide other buttons
            binding.btnMainFrame.visibility = android.view.View.GONE
            binding.btnMainPreset.visibility = android.view.View.GONE
            binding.btnMainBgReplace.visibility = android.view.View.GONE

            translateButtonToDock(binding.btnMainPortraitBlur)

            // Show ruler container & solid dock background
            binding.viewLeftDockBackground.visibility = android.view.View.VISIBLE
            binding.panelRulerContainer.visibility = android.view.View.VISIBLE
            binding.rvHorizontalTimeline.visibility = android.view.View.GONE
            binding.rvBackgroundOptions.visibility = android.view.View.GONE

            // Sync ruler value
            binding.panelRulerView.value = (blurIntensity * 100f).toInt()
            binding.tvPanelRulerValue.text = "${(blurIntensity * 100f).toInt()}"
        } else {
            isPortraitBlurModeOpen = false
            restoreMainMenuButtons()
            binding.viewLeftDockBackground.visibility = android.view.View.GONE
            binding.panelRulerContainer.visibility = android.view.View.GONE
        }
    }

    private fun toggleBgReplaceMode() {
        if (isCountingDown) return
        val wrapper = binding.buttonsWrapper
        TransitionManager.beginDelayedTransition(wrapper)

        if (!isBgReplaceModeOpen) {
            isBgReplaceModeOpen = true
            isFrameModeOpen = false
            isPresetModeOpen = false
            isPortraitBlurModeOpen = false

            // Hide other buttons
            binding.btnMainFrame.visibility = android.view.View.GONE
            binding.btnMainPreset.visibility = android.view.View.GONE
            binding.btnMainPortraitBlur.visibility = android.view.View.GONE

            translateButtonToDock(binding.btnMainBgReplace)

            // Show background options recycler view & solid dock background
            binding.viewLeftDockBackground.visibility = android.view.View.VISIBLE
            binding.rvBackgroundOptions.visibility = android.view.View.VISIBLE
            binding.rvHorizontalTimeline.visibility = android.view.View.GONE
            binding.panelRulerContainer.visibility = android.view.View.GONE
        } else {
            isBgReplaceModeOpen = false
            restoreMainMenuButtons()
            binding.viewLeftDockBackground.visibility = android.view.View.GONE
            binding.rvBackgroundOptions.visibility = android.view.View.GONE
        }
    }

    private fun openAdvancedSliders(selectedPreset: ColorPreset) {
        editingPreset = selectedPreset.copy(
            basic = selectedPreset.basic.copy(),
            advanced = selectedPreset.advanced.copy(),
            toneCurve = selectedPreset.toneCurve.copy(),
            grain = selectedPreset.grain.copy()
        )

        binding.layoutEditorTitleBar.visibility = android.view.View.VISIBLE
        
        // Setup initial selected attribute: Brightness
        val initialNode = AttributeNode(EditAttribute.BRIGHTNESS, "Brightness (Độ sáng)", R.drawable.ic_attr_brightness)
        onAttributeSelected(initialNode)

        binding.panelSelectionContainer.visibility = android.view.View.GONE
        binding.bottomControlPanel.visibility = android.view.View.GONE

        binding.layoutTimelineEditor.apply {
            visibility = android.view.View.VISIBLE
            alpha = 0f
            animate()
                .alpha(1f)
                .setDuration(250)
                .start()
        }
    }

    private fun closeAdvancedSliders() {
        binding.layoutTimelineEditor.animate()
            .alpha(0f)
            .setDuration(200)
            .withEndAction {
                binding.layoutTimelineEditor.visibility = android.view.View.GONE
                binding.layoutEditorTitleBar.visibility = android.view.View.GONE
                binding.panelSelectionContainer.visibility = android.view.View.VISIBLE
                binding.bottomControlPanel.visibility = android.view.View.VISIBLE
                editingPreset = null
                currentSelectedPreset?.let { applyPresetFilter(it) }
            }
            .start()
    }

    private fun onAttributeSelected(selectedNode: AttributeNode) {
        currentAttribute = selectedNode.type
        binding.tvCurrentAttributeName.text = selectedNode.displayName

        if (selectedNode.type == EditAttribute.BG_REPLACE) {
            binding.rulerView.visibility = android.view.View.GONE
            binding.layoutRulerPointer.visibility = android.view.View.GONE
            binding.rvBackgroundOptions.visibility = android.view.View.VISIBLE
        } else {
            binding.rulerView.visibility = android.view.View.VISIBLE
            binding.layoutRulerPointer.visibility = android.view.View.VISIBLE
            binding.rvBackgroundOptions.visibility = android.view.View.GONE
        }

        // Adjust Ruler limits based on attribute range
        val isNonNegative = selectedNode.type == EditAttribute.VIGNETTE_AMOUNT ||
                selectedNode.type == EditAttribute.FADED_BLACK_LEVEL ||
                selectedNode.type == EditAttribute.SHADOWS_TINT_R ||
                selectedNode.type == EditAttribute.SHADOWS_TINT_G ||
                selectedNode.type == EditAttribute.SHADOWS_TINT_B ||
                selectedNode.type == EditAttribute.GRAIN_AMOUNT ||
                selectedNode.type == EditAttribute.GRAIN_SIZE ||
                selectedNode.type == EditAttribute.PORTRAIT_BLUR

        binding.rulerView.minVal = if (isNonNegative) 0 else -100
        binding.rulerView.maxVal = 100

        val currentVal = (getCurrentAttributeValue() * 100f).toInt()
        binding.rulerView.value = currentVal
        binding.tvRulerValue.text = "$currentVal"

        attributeAdapter.updateSelection(selectedNode.type)
    }

    private fun getCurrentAttributeValue(): Float {
        if (currentAttribute == EditAttribute.PORTRAIT_BLUR) return blurIntensity
        if (currentAttribute == EditAttribute.BG_REPLACE) return 0f
        val preset = editingPreset ?: return 0f
        return when (currentAttribute) {
            EditAttribute.BRIGHTNESS -> preset.basic.brightness
            EditAttribute.CONTRAST -> preset.basic.contrast
            EditAttribute.SATURATION -> preset.basic.saturation
            EditAttribute.VIBRANCE -> preset.basic.vibrance
            EditAttribute.TEMPERATURE -> preset.basic.temperature
            EditAttribute.TINT -> preset.basic.tint
            EditAttribute.CLARITY -> preset.advanced.clarity
            EditAttribute.DEHAZE -> preset.advanced.dehaze
            EditAttribute.VIGNETTE_AMOUNT -> preset.advanced.vignetteAmount
            EditAttribute.FADED_BLACK_LEVEL -> preset.toneCurve.fadedBlackLevel
            EditAttribute.SHADOWS_TINT_R -> preset.toneCurve.shadowsTintR
            EditAttribute.SHADOWS_TINT_G -> preset.toneCurve.shadowsTintG
            EditAttribute.SHADOWS_TINT_B -> preset.toneCurve.shadowsTintB
            EditAttribute.GRAIN_AMOUNT -> preset.grain.grainAmount
            EditAttribute.GRAIN_SIZE -> preset.grain.grainSize
            else -> 0f
        }
    }

    private fun setCurrentAttributeValue(value: Float) {
        if (currentAttribute == EditAttribute.PORTRAIT_BLUR) {
            blurIntensity = value.coerceIn(0f, 1f)
            return
        }
        if (currentAttribute == EditAttribute.BG_REPLACE) return
        val preset = editingPreset ?: return
        when (currentAttribute) {
            EditAttribute.BRIGHTNESS -> preset.basic.brightness = value
            EditAttribute.CONTRAST -> preset.basic.contrast = value
            EditAttribute.SATURATION -> preset.basic.saturation = value
            EditAttribute.VIBRANCE -> preset.basic.vibrance = value
            EditAttribute.TEMPERATURE -> preset.basic.temperature = value
            EditAttribute.TINT -> preset.basic.tint = value
            EditAttribute.CLARITY -> preset.advanced.clarity = value
            EditAttribute.DEHAZE -> preset.advanced.dehaze = value
            EditAttribute.VIGNETTE_AMOUNT -> preset.advanced.vignetteAmount = value.coerceIn(0f, 1f)
            EditAttribute.FADED_BLACK_LEVEL -> preset.toneCurve.fadedBlackLevel = value.coerceIn(0f, 1f)
            EditAttribute.SHADOWS_TINT_R -> preset.toneCurve.shadowsTintR = value.coerceIn(0f, 1f)
            EditAttribute.SHADOWS_TINT_G -> preset.toneCurve.shadowsTintG = value.coerceIn(0f, 1f)
            EditAttribute.SHADOWS_TINT_B -> preset.toneCurve.shadowsTintB = value.coerceIn(0f, 1f)
            EditAttribute.GRAIN_AMOUNT -> preset.grain.grainAmount = value.coerceIn(0f, 1f)
            EditAttribute.GRAIN_SIZE -> preset.grain.grainSize = value.coerceIn(0f, 1f)
            else -> {}
        }
    }

    private fun showSavePresetDialog(tempPreset: ColorPreset) {
        val input = android.widget.EditText(this).apply {
            hint = "Nhập tên Preset mới"
        }
        
        val container = android.widget.FrameLayout(this).apply {
            setPadding(50, 20, 50, 20)
            addView(input)
        }

        androidx.appcompat.app.AlertDialog.Builder(this)
            .setTitle("Lưu Preset cá nhân")
            .setView(container)
            .setPositiveButton("Lưu") { _, _ ->
                val name = input.text.toString().trim()
                if (name.isNotEmpty()) {
                    saveCustomPreset(tempPreset, name)
                } else {
                    Toast.makeText(this, "Tên không được để trống", Toast.LENGTH_SHORT).show()
                }
            }
            .setNegativeButton("Hủy", null)
            .show()
    }

    private fun saveCustomPreset(tempPreset: ColorPreset, newName: String) {
        val customId = "custom_" + System.currentTimeMillis()
        val customPreset = ColorPreset(
            id = customId,
            name = newName,
            themeCategory = "Custom",
            isEditable = true,
            basic = BasicAdjustments(
                tempPreset.basic.brightness,
                tempPreset.basic.contrast,
                tempPreset.basic.saturation,
                tempPreset.basic.vibrance,
                tempPreset.basic.temperature,
                tempPreset.basic.tint
            ),
            advanced = AdvancedEffects(
                tempPreset.advanced.clarity,
                tempPreset.advanced.dehaze,
                tempPreset.advanced.vignetteAmount
            ),
            toneCurve = ToneCurveEmulation(
                tempPreset.toneCurve.fadedBlackLevel,
                tempPreset.toneCurve.shadowsTintR,
                tempPreset.toneCurve.shadowsTintG,
                tempPreset.toneCurve.shadowsTintB
            ),
            grain = FilmGrain(
                tempPreset.grain.grainAmount,
                tempPreset.grain.grainSize
            )
        )

        // Serialize to JSON format manually (incorporating all 14 parameters)
        val jsonString = """
        {
          "id": "$customId",
          "name": "$newName",
          "theme_category": "Custom",
          "is_editable": true,
          "basic_adjustments": {
            "brightness": ${customPreset.basic.brightness},
            "contrast": ${customPreset.basic.contrast},
            "saturation": ${customPreset.basic.saturation},
            "vibrance": ${customPreset.basic.vibrance},
            "temperature": ${customPreset.basic.temperature},
            "tint": ${customPreset.basic.tint}
          },
          "advanced_effects": {
            "clarity": ${customPreset.advanced.clarity},
            "dehaze": ${customPreset.advanced.dehaze},
            "vignette_amount": ${customPreset.advanced.vignetteAmount}
          },
          "tone_curve_emulation": {
            "faded_black_level": ${customPreset.toneCurve.fadedBlackLevel},
            "shadows_tint_r": ${customPreset.toneCurve.shadowsTintR},
            "shadows_tint_g": ${customPreset.toneCurve.shadowsTintG},
            "shadows_tint_b": ${customPreset.toneCurve.shadowsTintB}
          },
          "film_grain": {
            "grain_amount": ${customPreset.grain.grainAmount},
            "grain_size": ${customPreset.grain.grainSize}
          }
        }
        """.trimIndent()

        try {
            val file = java.io.File(filesDir, "$customId.json")
            file.writeText(jsonString)
            
            // Dynamic UI update
            colorPresets = colorPresets + customPreset
            presetAdapter.updatePresets(colorPresets)
            Toast.makeText(this, "Đã lưu preset: $newName", Toast.LENGTH_SHORT).show()
            closeAdvancedSliders()
        } catch (e: Exception) {
            Toast.makeText(this, "Không thể lưu Preset file", Toast.LENGTH_SHORT).show()
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview configuration
            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
            }

            // ImageCapture configuration
            imageCapture = ImageCapture.Builder()
                .setFlashMode(flashMode)
                .build()

            // ImageAnalysis configuration
            val imageAnalysis = androidx.camera.core.ImageAnalysis.Builder()
                .setBackpressureStrategy(androidx.camera.core.ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .setOutputImageFormat(androidx.camera.core.ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
                .setTargetResolution(android.util.Size(480, 640))
                .build()

            imageAnalysis.setAnalyzer(cameraExecutor) { imageProxy ->
                val isBgActive = (selectedBgAssetPath != null) || (blurIntensity > 0f)
                if (isBgActive) {
                    val rawProxyBitmap = imageProxy.toBitmap()
                    val rotationDegrees = imageProxy.imageInfo.rotationDegrees
                    val processed = processPreviewFrame(rawProxyBitmap, rotationDegrees)
                    
                    runOnUiThread {
                        binding.imgAiPreviewOverlay.setImageBitmap(processed)
                        binding.imgAiPreviewOverlay.visibility = android.view.View.VISIBLE
                    }
                    if (processed != rawProxyBitmap) {
                        rawProxyBitmap.recycle()
                    }
                } else {
                    runOnUiThread {
                        binding.imgAiPreviewOverlay.visibility = android.view.View.GONE
                    }
                }
                imageProxy.close()
            }

            try {
                cameraProvider.unbindAll()
                
                // Bind usecases to Lifecycle
                val camera = cameraProvider.bindToLifecycle(
                    this, lensFacing, preview, imageCapture, imageAnalysis
                )
                
                cameraControl = camera.cameraControl
                cameraInfo = camera.cameraInfo

                // Reset zoom to 1.0x on start/switch
                applyZoom(1.0f)
            } catch (xc: Exception) {
                val errorMsg = getString(R.string.toast_camera_start_error)
                Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
            }
        }, ContextCompat.getMainExecutor(this))
    }

    private fun toggleCamera() {
        if (isCountingDown) return
        lensFacing = if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) {
            CameraSelector.DEFAULT_BACK_CAMERA
        } else {
            CameraSelector.DEFAULT_FRONT_CAMERA
        }
        
        // Reset Selector UI states
        if (isFrameModeOpen) toggleFrameMode()
        if (isPresetModeOpen) togglePresetMode()
        if (isPortraitBlurModeOpen) togglePortraitBlurMode()
        if (isBgReplaceModeOpen) toggleBgReplaceMode()
        setFrameOverlay("")
        applyPresetFilter(colorPresets[0])
        updateFrameModeIcon(FrameItem("DEFAULT", "", ""))
        updatePresetModeIcon(colorPresets[0])

        startCamera()
    }

    private fun toggleFlash() {
        flashMode = when (flashMode) {
            ImageCapture.FLASH_MODE_OFF -> ImageCapture.FLASH_MODE_ON
            ImageCapture.FLASH_MODE_ON -> ImageCapture.FLASH_MODE_AUTO
            else -> ImageCapture.FLASH_MODE_OFF
        }
        updateFlashIconState(flashMode)
        imageCapture?.flashMode = flashMode
    }

    private fun updateFlashIconState(flashMode: Int) {
        val color = if (flashMode == ImageCapture.FLASH_MODE_OFF) {
            ContextCompat.getColor(this, R.color.theme_white)
        } else {
            ContextCompat.getColor(this, R.color.theme_primary)
        }
        binding.btnFlash.imageTintList = android.content.res.ColorStateList.valueOf(color)
    }

    private fun toggleTimer() {
        if (isCountingDown) return
        timerSeconds = when (timerSeconds) {
            0 -> 3
            3 -> 5
            5 -> 10
            else -> 0
        }
        updateTimerIconState(timerSeconds)
    }

    private fun updateTimerIconState(timerSecs: Int) {
        val color = if (timerSecs == 0) {
            ContextCompat.getColor(this, R.color.theme_white)
        } else {
            ContextCompat.getColor(this, R.color.theme_primary)
        }
        binding.btnTimer.imageTintList = android.content.res.ColorStateList.valueOf(color)
    }

    private fun applyZoom(ratio: Float) {
        cameraInfo?.zoomState?.value?.let { currentState ->
            val maxZoom = currentState.maxZoomRatio
            val minZoom = currentState.minZoomRatio

            if (ratio in minZoom..maxZoom) {
                cameraControl?.setZoomRatio(ratio)
                updateZoomButtonsUI(ratio)
            } else {
                val errorMsg = getString(R.string.toast_zoom_unsupported, ratio)
                Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
            }
        } ?: run {
            // Fallback for emulator where zoomState might be null on start
            cameraControl?.setZoomRatio(ratio)
            updateZoomButtonsUI(ratio)
        }
    }

    private fun updateZoomButtonsUI(ratio: Float) {
        val selectedTv = when (ratio) {
            0.5f -> binding.tvZoom05
            1.0f -> binding.tvZoom1
            3.0f -> binding.tvZoom3
            else -> binding.tvZoom1
        }
        updateZoomTextState(selectedTv)
    }

    private fun updateZoomTextState(selectedZoom: android.widget.TextView) {
        binding.tvZoom05.setTextColor(ContextCompat.getColor(this, R.color.theme_white))
        binding.tvZoom1.setTextColor(ContextCompat.getColor(this, R.color.theme_white))
        binding.tvZoom3.setTextColor(ContextCompat.getColor(this, R.color.theme_white))

        selectedZoom.setTextColor(ContextCompat.getColor(this, R.color.theme_primary))
    }

    private fun onCaptureClick() {
        if (isCountingDown) return

        if (timerSeconds > 0) {
            startCountDown()
        } else {
            takePicture()
        }
    }

    private fun startCountDown() {
        isCountingDown = true
        binding.txtTimerCountdown.visibility = android.view.View.VISIBLE

        val timer = object : android.os.CountDownTimer(timerSeconds * 1000L, 1000L) {
            override fun onTick(millisUntilFinished: Long) {
                val secondsRemaining = (millisUntilFinished / 1000L) + 1
                binding.txtTimerCountdown.text = secondsRemaining.toString()
                try {
                    val toneG = android.media.ToneGenerator(android.media.AudioManager.STREAM_ALARM, 50)
                    toneG.startTone(android.media.ToneGenerator.TONE_PROP_BEEP, 100)
                } catch (e: Exception) {
                    // Fallback
                }
            }

            override fun onFinish() {
                binding.txtTimerCountdown.visibility = android.view.View.GONE
                isCountingDown = false
                takePicture()
            }
        }
        timer.start()
    }

    private fun takePicture() {
        val imageCapture = this.imageCapture ?: return

        // 1. Show flash overlay for 100ms
        runOnUiThread {
            binding.viewShutterFlash.visibility = android.view.View.VISIBLE
            binding.viewShutterFlash.postDelayed({
                binding.viewShutterFlash.visibility = android.view.View.GONE
            }, 100)
        }

        // Setup output file
        val tempFile = java.io.File.createTempFile("raw_capture_", ".jpg", cacheDir)
        val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build()

        imageCapture.takePicture(
            outputOptions,
            cameraExecutor,
            object : ImageCapture.OnImageSavedCallback {
                override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                    // Update gallery button thumbnail instantly
                    try {
                        val rawBitmap = android.graphics.BitmapFactory.decodeFile(tempFile.absolutePath)
                        if (rawBitmap != null) {
                            val thumb = android.graphics.Bitmap.createScaledBitmap(rawBitmap, 128, 128, true)
                            runOnUiThread {
                                binding.btnGalleryReview.setImageBitmap(thumb)
                            }
                            if (thumb != rawBitmap) {
                                rawBitmap.recycle()
                            }
                        }
                    } catch (e: Exception) {
                        // Ignore thumbnail decoding errors
                    }

                    // Process and save in the background
                    processAndSaveCapturedPhoto(tempFile)
                }

                override fun onError(exception: ImageCaptureException) {
                    runOnUiThread {
                        Toast.makeText(this@MainActivity, "Lỗi chụp ảnh: ${exception.message}", Toast.LENGTH_SHORT).show()
                    }
                    if (tempFile.exists()) {
                        tempFile.delete()
                    }
                }
            }
        )
    }

    private fun processAndSaveCapturedPhoto(file: java.io.File) {
        try {
            // 1. Decode captured file to Bitmap
            val rawBitmap = android.graphics.BitmapFactory.decodeFile(file.absolutePath)
            if (rawBitmap == null) {
                runOnUiThread {
                    Toast.makeText(this, "Không thể đọc dữ liệu ảnh chụp", Toast.LENGTH_SHORT).show()
                }
                file.delete()
                return
            }

            // 2. Adjust Orientation (EXIF + Mirroring for Front Camera)
            var processedBitmap = rawBitmap
            try {
                val exif = android.media.ExifInterface(file.absolutePath)
                val orientation = exif.getAttributeInt(
                    android.media.ExifInterface.TAG_ORIENTATION,
                    android.media.ExifInterface.ORIENTATION_NORMAL
                )
                val matrix = android.graphics.Matrix()
                var rotation = 0f
                when (orientation) {
                    android.media.ExifInterface.ORIENTATION_ROTATE_90 -> rotation = 90f
                    android.media.ExifInterface.ORIENTATION_ROTATE_180 -> rotation = 180f
                    android.media.ExifInterface.ORIENTATION_ROTATE_270 -> rotation = 270f
                }
                if (rotation != 0f) {
                    matrix.postRotate(rotation)
                }
                
                // Front camera mirroring
                if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) {
                    matrix.postScale(-1f, 1f)
                }
                
                if (rotation != 0f || lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) {
                    processedBitmap = android.graphics.Bitmap.createBitmap(
                        rawBitmap, 0, 0, rawBitmap.width, rawBitmap.height, matrix, true
                    )
                    if (processedBitmap != rawBitmap) {
                        rawBitmap.recycle()
                    }
                }
            } catch (exifEx: Exception) {
                // Ignore exif exceptions
            }

            // 2.5. AI Portrait Blur / Background Replacement
            val isBgActive = (selectedBgAssetPath != null) || (blurIntensity > 0f)
            if (isBgActive) {
                val aiBitmap = processAdvancedBackgroundAi(processedBitmap)
                if (aiBitmap != processedBitmap) {
                    processedBitmap = aiBitmap
                }
            }

            // 3. Apply Current Preset Filters (ColorMatrix + Semi-transparent overlay color)
            val preset = currentSelectedPreset ?: colorPresets[0]
            
            val filteredBitmap = android.graphics.Bitmap.createBitmap(
                processedBitmap.width, processedBitmap.height, android.graphics.Bitmap.Config.ARGB_8888
            )
            val canvas = android.graphics.Canvas(filteredBitmap)
            val paint = android.graphics.Paint().apply {
                isAntiAlias = true
                isFilterBitmap = true
            }
            
            if (preset.id != "DEFAULT") {
                val finalMatrix = getColorMatrixForPreset(preset)
                paint.colorFilter = android.graphics.ColorMatrixColorFilter(finalMatrix)
            }
            canvas.drawBitmap(processedBitmap, 0f, 0f, paint)
            processedBitmap.recycle()

            val overlayColor = getFilterColorForPreset(preset)
            if (overlayColor != android.graphics.Color.TRANSPARENT) {
                val overlayPaint = android.graphics.Paint().apply {
                    color = overlayColor
                    style = android.graphics.Paint.Style.FILL
                }
                canvas.drawRect(
                    0f, 0f, filteredBitmap.width.toFloat(), filteredBitmap.height.toFloat(), overlayPaint
                )
            }

            // 4. Film Grain effect
            if (preset.grain.grainAmount > 0f) {
                val grainAmount = preset.grain.grainAmount
                val grainSize = preset.grain.grainSize.coerceAtLeast(0.01f)
                val width = filteredBitmap.width
                val height = filteredBitmap.height
                val pixels = IntArray(width * height)
                filteredBitmap.getPixels(pixels, 0, width, 0, 0, width, height)
                
                val random = java.util.Random()
                val sizeFactor = (grainSize * 12).toInt().coerceAtLeast(1)
                
                for (y in 0 until height step sizeFactor) {
                    for (x in 0 until width step sizeFactor) {
                        val noise = (random.nextGaussian() * 127 * grainAmount).toInt()
                        for (ny in 0 until sizeFactor) {
                            if (y + ny >= height) break
                            for (nx in 0 until sizeFactor) {
                                if (x + nx >= width) break
                                val index = (y + ny) * width + (x + nx)
                                val color = pixels[index]
                                val r = (android.graphics.Color.red(color) + noise).coerceIn(0, 255)
                                val g = (android.graphics.Color.green(color) + noise).coerceIn(0, 255)
                                val b = (android.graphics.Color.blue(color) + noise).coerceIn(0, 255)
                                pixels[index] = android.graphics.Color.rgb(r, g, b)
                            }
                        }
                    }
                }
                filteredBitmap.setPixels(pixels, 0, width, 0, 0, width, height)
            }

            // 5. Merge Instax Frame Overlay
            var finalBitmap = filteredBitmap
            val framePath = currentSelectedFramePath
            if (framePath != null && framePath.isNotEmpty()) {
                try {
                    val frameInputStream = assets.open(framePath)
                    val frameBitmap = android.graphics.BitmapFactory.decodeStream(frameInputStream)
                    if (frameBitmap != null) {
                        val W_frame = frameBitmap.width
                        val H_frame = frameBitmap.height
                        val R = W_frame.toFloat() / H_frame.toFloat()

                        val W_photo = filteredBitmap.width
                        val H_photo = filteredBitmap.height
                        val photoRatio = W_photo.toFloat() / H_photo.toFloat()
                        
                        var cropX = 0
                        var cropY = 0
                        var cropW = W_photo
                        var cropH = H_photo

                        if (photoRatio > R) {
                            cropW = (H_photo * R).toInt()
                            cropX = (W_photo - cropW) / 2
                        } else {
                            cropH = (W_photo / R).toInt()
                            cropY = (H_photo - cropH) / 2
                        }

                        val croppedPhoto = android.graphics.Bitmap.createBitmap(
                            filteredBitmap, cropX, cropY, cropW, cropH
                        )
                        val resizedPhoto = android.graphics.Bitmap.createScaledBitmap(
                            croppedPhoto, W_frame, H_frame, true
                        )
                        
                        if (croppedPhoto != filteredBitmap) {
                            croppedPhoto.recycle()
                        }
                        filteredBitmap.recycle()

                        val mergeBitmap = android.graphics.Bitmap.createBitmap(
                            W_frame, H_frame, android.graphics.Bitmap.Config.ARGB_8888
                        )
                        val mergeCanvas = android.graphics.Canvas(mergeBitmap)
                        mergeCanvas.drawBitmap(resizedPhoto, 0f, 0f, null)
                        mergeCanvas.drawBitmap(frameBitmap, 0f, 0f, null)
                        
                        resizedPhoto.recycle()
                        frameBitmap.recycle()
                        finalBitmap = mergeBitmap
                    }
                } catch (e: Exception) {
                    // ignore
                }
            }

            // 6. Save image to system Gallery
            val savedSuccess = saveBitmapToGallery(finalBitmap, "RetroPhoto")
            finalBitmap.recycle()

            runOnUiThread {
                if (!savedSuccess) {
                    Toast.makeText(this, "Lỗi khi lưu ảnh vào Gallery", Toast.LENGTH_SHORT).show()
                }
            }
        } catch (e: Exception) {
            runOnUiThread {
                Toast.makeText(this, "Lỗi xử lý ảnh: ${e.message}", Toast.LENGTH_SHORT).show()
            }
        } finally {
            if (file.exists()) {
                file.delete()
            }
        }
    }

    private fun saveBitmapToGallery(bitmap: android.graphics.Bitmap, title: String): Boolean {
        val filename = "${title}_${System.currentTimeMillis()}.jpg"
        var success = false
        
        val contentResolver = contentResolver
        val imageDetails = android.content.ContentValues().apply {
            put(android.provider.MediaStore.Images.Media.DISPLAY_NAME, filename)
            put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                put(android.provider.MediaStore.Images.Media.RELATIVE_PATH, "Pictures/RetroPhotobooth")
                put(android.provider.MediaStore.Images.Media.IS_PENDING, 1)
            }
        }
        
        val imageUri = contentResolver.insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageDetails)
        if (imageUri != null) {
            try {
                contentResolver.openOutputStream(imageUri).use { outputStream ->
                    if (outputStream != null) {
                        bitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 95, outputStream)
                        success = true
                    }
                }
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                    imageDetails.clear()
                    imageDetails.put(android.provider.MediaStore.Images.Media.IS_PENDING, 0)
                    contentResolver.update(imageUri, imageDetails, null, null)
                }
            } catch (e: Exception) {
                contentResolver.delete(imageUri, null, null)
                success = false
            }
        }
        return success
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                val errorMsg = getString(R.string.toast_camera_permission_denied)
                Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun processPreviewFrame(src: android.graphics.Bitmap, rotationDegrees: Int): android.graphics.Bitmap {
        val matrix = android.graphics.Matrix()
        if (rotationDegrees != 0) {
            matrix.postRotate(rotationDegrees.toFloat())
        }
        if (lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA) {
            matrix.postScale(-1f, 1f)
        }
        val rotated = android.graphics.Bitmap.createBitmap(src, 0, 0, src.width, src.height, matrix, true)
        
        // Tối ưu hóa giật lag: Downscale ảnh preview xuống 2 lần để giảm tải CPU xử lý vòng lặp pixel
        val scaleFactor = 2
        val downscaledW = (rotated.width / scaleFactor).coerceAtLeast(1)
        val downscaledH = (rotated.height / scaleFactor).coerceAtLeast(1)
        val downscaled = android.graphics.Bitmap.createScaledBitmap(rotated, downscaledW, downscaledH, true)
        if (downscaled != rotated) {
            rotated.recycle()
        }
        
        val ai = processAdvancedBackgroundAi(downscaled, isStreamMode = true)
        if (ai != downscaled) {
            downscaled.recycle()
        }
        return ai
    }

    private fun processAdvancedBackgroundAi(
        capturedImage: android.graphics.Bitmap,
        isStreamMode: Boolean = false
    ): android.graphics.Bitmap {
        val isBgActive = (selectedBgAssetPath != null) || (blurIntensity > 0f)
        if (!isBgActive) {
            return capturedImage
        }

        if (!isAiSupported) {
            if (blurIntensity > 0f) {
                val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
                val radius = (40 * opticalIntensity).toInt().coerceIn(1, 40)
                return blurBitmap(capturedImage, radius)
            }
            return capturedImage
        }

        // BỆNH PHÁP KHẮC PHỤC TRÌỆT ĐỂ LAG: Nếu là luồng Live Viewfinder, không chạy mô hình AI nặng
        if (isStreamMode && isAiSupported) {
            try {
                val segmenter = getImageSegmenter()
                if (segmenter != null) {
                    val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(capturedImage).build()
                    val segmentationResult = synchronized(aiInferenceLock) {
                        segmenter.segment(mpImage)
                    }
                    val categoryMaskOptional = segmentationResult.categoryMask()
                    if (categoryMaskOptional.isPresent) {
                        val categoryMask = categoryMaskOptional.get()
                        val categoryMaskBuffer = ByteBufferExtractor.extract(categoryMask)
                        categoryMaskBuffer.rewind()
                        
                        val w = categoryMask.width
                        val h = categoryMask.height
                        val bufferCapacity = categoryMaskBuffer.capacity()

                        // Cache mask bytes for Touch-to-Focus
                        val rawBytes = ByteArray(bufferCapacity)
                        categoryMaskBuffer.rewind()
                        categoryMaskBuffer.get(rawBytes)
                        cachedCategoryMask = rawBytes
                        cachedMaskWidth = w
                        cachedMaskHeight = h
                        categoryMaskBuffer.rewind()
                        
                        // Create and blur alpha mask with Touch-to-Focus class selection
                        val focusClassId = if (userSelectedClassFocus >= 0) userSelectedClassFocus else -1
                        val alphaMask = FloatArray(w * h)
                        for (i in 0 until (w * h)) {
                            val classId = if (i < bufferCapacity) rawBytes[i].toInt() else 0
                            alphaMask[i] = if (focusClassId >= 0) {
                                if (classId == focusClassId) 1.0f else 0.0f
                            } else {
                                if (classId in 1..5) 1.0f else 0.0f
                            }
                        }
                        blurAlphaMaskBox(alphaMask, w, h, radius = 3)
                        
                        // Prepare background (camera preview or custom background asset)
                        val baseBg = if (selectedBgAssetPath != null) {
                            try {
                                val bgInputStream = assets.open(selectedBgAssetPath!!)
                                val decodedBg = android.graphics.BitmapFactory.decodeStream(bgInputStream)
                                if (decodedBg != null) {
                                    val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
                                    if (tempScaled != decodedBg) decodedBg.recycle()
                                    tempScaled
                                } else {
                                    val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                                    solidBg.eraseColor(android.graphics.Color.BLACK)
                                    solidBg
                                }
                            } catch (e: Exception) {
                                val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                                solidBg.eraseColor(android.graphics.Color.BLACK)
                                solidBg
                            }
                        } else {
                            capturedImage
                        }
                        
                        val bgPixels = IntArray(w * h)
                        var blurredBg: android.graphics.Bitmap? = null
                        if (blurIntensity > 0f) {
                            val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
                            val radius = (25 * opticalIntensity).toInt().coerceIn(1, 25)
                            blurredBg = blurBitmap(baseBg, radius)
                            blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
                        } else {
                            baseBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
                        }
                        
                        if (baseBg != capturedImage) {
                            baseBg.recycle()
                        }
                        
                        val fgPixels = IntArray(w * h)
                        val outputPixels = IntArray(w * h)
                        capturedImage.getPixels(fgPixels, 0, w, 0, 0, w, h)
                        
                        for (idx in 0 until (w * h)) {
                            val alpha = alphaMask[idx]
                            
                            val fgPixel = fgPixels[idx]
                            var fgR = android.graphics.Color.red(fgPixel)
                            var fgG = android.graphics.Color.green(fgPixel)
                            var fgB = android.graphics.Color.blue(fgPixel)
                            
                            val bgPixel = bgPixels[idx]
                            val bgR = android.graphics.Color.red(bgPixel)
                            val bgG = android.graphics.Color.green(bgPixel)
                            val bgB = android.graphics.Color.blue(bgPixel)
                            
                            // Color decontamination for edges
                            if (alpha > 0f && alpha < 1.0f) {
                                val grayIntensity = (fgR + fgG + fgB) / 3f
                                fgR = (fgR * alpha + grayIntensity * (1f - alpha)).toInt()
                                fgG = (fgG * alpha + grayIntensity * (1f - alpha)).toInt()
                                fgB = (fgB * alpha + grayIntensity * (1f - alpha)).toInt()
                            }
                            
                            val outR = (fgR * alpha + bgR * (1f - alpha)).toInt().coerceIn(0, 255)
                            val outG = (fgG * alpha + bgG * (1f - alpha)).toInt().coerceIn(0, 255)
                            val outB = (fgB * alpha + bgB * (1f - alpha)).toInt().coerceIn(0, 255)
                            
                            outputPixels[idx] = android.graphics.Color.rgb(outR, outG, outB)
                        }
                        
                        blurredBg?.recycle()
                        
                        val outputBitmap = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                        outputBitmap.setPixels(outputPixels, 0, w, 0, 0, w, h)
                        return outputBitmap
                    }
                }
            } catch (e: Exception) {
                // Ignore and fallback
            }
            
            if (blurIntensity > 0f) {
                val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
                val radius = (25 * opticalIntensity).toInt().coerceIn(1, 25)
                return blurBitmap(capturedImage, radius)
            }
            return capturedImage
        }
        
        try {
            val segmenter = getImageSegmenter()
            if (segmenter != null) {
                val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(capturedImage).build()
                val segmentationResult = synchronized(aiInferenceLock) {
                    segmenter.segment(mpImage)
                }
                val categoryMaskOptional = segmentationResult.categoryMask()
                if (categoryMaskOptional.isPresent) {
                    val categoryMask = categoryMaskOptional.get()
                    val categoryMaskBuffer = ByteBufferExtractor.extract(categoryMask)
                    categoryMaskBuffer.rewind()
                    
                    val w = categoryMask.width
                    val h = categoryMask.height
                    val bufferCapacity = categoryMaskBuffer.capacity()
                    
                    // Create and blur alpha mask with Touch-to-Focus class selection
                    val focusClassId = if (userSelectedClassFocus >= 0) userSelectedClassFocus else -1
                    val alphaMask = FloatArray(w * h)
                    for (i in 0 until (w * h)) {
                        val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
                        alphaMask[i] = if (focusClassId >= 0) {
                            if (classId == focusClassId) 1.0f else 0.0f
                        } else {
                            if (classId in 1..5) 1.0f else 0.0f
                        }
                    }
                    blurAlphaMaskBox(alphaMask, w, h, radius = 5)
                    
                    // Prepare background (camera preview or custom background asset)
                    val baseBg = if (selectedBgAssetPath != null) {
                        try {
                            val bgInputStream = assets.open(selectedBgAssetPath!!)
                            val decodedBg = android.graphics.BitmapFactory.decodeStream(bgInputStream)
                            if (decodedBg != null) {
                                val tempScaled = android.graphics.Bitmap.createScaledBitmap(decodedBg, w, h, true)
                                if (tempScaled != decodedBg) decodedBg.recycle()
                                tempScaled
                            } else {
                                val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                                solidBg.eraseColor(android.graphics.Color.BLACK)
                                solidBg
                            }
                        } catch (e: Exception) {
                            val solidBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                            solidBg.eraseColor(android.graphics.Color.BLACK)
                            solidBg
                        }
                    } else {
                        // Triệt tiêu Ghost Shadow (Bóng ma rế viền) bằng giãn cơ thể ẩn (Inpainting Dilate)
                        val cleanBg = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                        for (y in 0 until h) {
                            for (x in 0 until w) {
                                val idx = y * w + x
                                val alpha = alphaMask[idx]
                                if (alpha > 0.01f) {
                                    cleanBg.setPixel(x, y, getNearestBackgroundPixel(capturedImage, alphaMask, x, y, w, h))
                                } else {
                                    cleanBg.setPixel(x, y, capturedImage.getPixel(x, y))
                                }
                            }
                        }
                        cleanBg
                    }
                    
                    val bgPixels = IntArray(w * h)
                    var blurredBg: android.graphics.Bitmap? = null
                    if (blurIntensity > 0f) {
                        val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
                        val radius = (40 * opticalIntensity).toInt().coerceIn(1, 40)
                        blurredBg = blurBitmap(baseBg, radius)
                        blurredBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
                    } else {
                        baseBg.getPixels(bgPixels, 0, w, 0, 0, w, h)
                    }
                    
                    if (baseBg != capturedImage) {
                        baseBg.recycle()
                    }
                    
                    val fgPixels = IntArray(w * h)
                    val outputPixels = IntArray(w * h)
                    capturedImage.getPixels(fgPixels, 0, w, 0, 0, w, h)
                    
                    for (idx in 0 until (w * h)) {
                        val alpha = alphaMask[idx]
                        
                        val fgPixel = fgPixels[idx]
                        var fgR = android.graphics.Color.red(fgPixel)
                        var fgG = android.graphics.Color.green(fgPixel)
                        var fgB = android.graphics.Color.blue(fgPixel)
                        
                        val bgPixel = bgPixels[idx]
                        val bgR = android.graphics.Color.red(bgPixel)
                        val bgG = android.graphics.Color.green(bgPixel)
                        val bgB = android.graphics.Color.blue(bgPixel)
                        
                        // Color decontamination for edges
                        if (alpha > 0f && alpha < 1.0f) {
                            val grayIntensity = (fgR + fgG + fgB) / 3f
                            fgR = (fgR * alpha + grayIntensity * (1f - alpha)).toInt()
                            fgG = (fgG * alpha + grayIntensity * (1f - alpha)).toInt()
                            fgB = (fgB * alpha + grayIntensity * (1f - alpha)).toInt()
                        }
                        
                        val outR = (fgR * alpha + bgR * (1f - alpha)).toInt().coerceIn(0, 255)
                        val outG = (fgG * alpha + bgG * (1f - alpha)).toInt().coerceIn(0, 255)
                        val outB = (fgB * alpha + bgB * (1f - alpha)).toInt().coerceIn(0, 255)
                        
                        outputPixels[idx] = android.graphics.Color.rgb(outR, outG, outB)
                    }
                    
                    blurredBg?.recycle()
                    
                    val outputBitmap = android.graphics.Bitmap.createBitmap(w, h, android.graphics.Bitmap.Config.ARGB_8888)
                    outputBitmap.setPixels(outputPixels, 0, w, 0, 0, w, h)
                    return outputBitmap
                }
            }
        } catch (e: Exception) {
            // Ignore and fallback
        }
        
        if (blurIntensity > 0f) {
            val opticalIntensity = Math.pow(blurIntensity.toDouble(), 2.5).toFloat()
            val radius = (40 * opticalIntensity).toInt().coerceIn(1, 40)
            return blurBitmap(capturedImage, radius)
        }
        return capturedImage
    }

    private fun getNearestBackgroundPixel(
        src: android.graphics.Bitmap,
        alphaMask: FloatArray,
        startX: Int,
        startY: Int,
        w: Int,
        h: Int
    ): Int {
        val maxDist = 30
        for (r in 1..maxDist) {
            // Check 4 directions: left, right, up, down
            var px = startX - r
            if (px >= 0) {
                if (alphaMask[startY * w + px] <= 0.01f) return src.getPixel(px, startY)
            }
            px = startX + r
            if (px < w) {
                if (alphaMask[startY * w + px] <= 0.01f) return src.getPixel(px, startY)
            }
            var py = startY - r
            if (py >= 0) {
                if (alphaMask[py * w + startX] <= 0.01f) return src.getPixel(startX, py)
            }
            py = startY + r
            if (py < h) {
                if (alphaMask[py * w + startX] <= 0.01f) return src.getPixel(startX, py)
            }
        }
        return src.getPixel(0, 0)
    }

    private fun blurAlphaMaskBox(arr: FloatArray, w: Int, h: Int, radius: Int) {
        if (radius <= 0) return
        val temp = FloatArray(w * h)
        
        // Horizontal pass
        for (y in 0 until h) {
            val rowOffset = y * w
            var sum = 0f
            val windowSize = radius * 2 + 1
            
            // Initialize window sum
            for (dx in -radius..radius) {
                val px = dx.coerceIn(0, w - 1)
                sum += arr[rowOffset + px]
            }
            temp[rowOffset] = sum / windowSize
            
            // Slide window
            for (x in 1 until w) {
                val oldPixelX = (x - 1 - radius).coerceIn(0, w - 1)
                val newPixelX = (x + radius).coerceIn(0, w - 1)
                sum = sum - arr[rowOffset + oldPixelX] + arr[rowOffset + newPixelX]
                temp[rowOffset + x] = sum / windowSize
            }
        }
        
        // Vertical pass
        for (x in 0 until w) {
            var sum = 0f
            val windowSize = radius * 2 + 1
            
            // Initialize window sum
            for (dy in -radius..radius) {
                val py = dy.coerceIn(0, h - 1)
                sum += temp[py * w + x]
            }
            arr[x] = sum / windowSize
            
            // Slide window
            for (y in 1 until h) {
                val oldPixelY = (y - 1 - radius).coerceIn(0, h - 1)
                val newPixelY = (y + radius).coerceIn(0, h - 1)
                sum = sum - temp[oldPixelY * w + x] + temp[newPixelY * w + x]
                arr[y * w + x] = sum / windowSize
            }
        }
    }

    private fun cubicSmoothStep(edge0: Float, edge1: Float, x: Float): Float {
        val t = ((x - edge0) / (edge1 - edge0)).coerceIn(0f, 1f)
        return t * t * (3f - 2f * t)
    }

    private fun blurBitmap(src: android.graphics.Bitmap, radius: Int): android.graphics.Bitmap {
        val w = src.width
        val h = src.height
        
        // Resolution-independent downscaling for consistent blur appearance and extremely high performance
        val targetW = 120
        val targetH = (h * targetW / w).coerceAtLeast(1)
        
        val downscaled = android.graphics.Bitmap.createScaledBitmap(src, targetW, targetH, true)
        val r = radius.coerceIn(1, targetW / 2)
        
        val pixels = IntArray(targetW * targetH)
        downscaled.getPixels(pixels, 0, targetW, 0, 0, targetW, targetH)
        downscaled.recycle()
        
        val temp = IntArray(targetW * targetH)
        val windowSize = r * 2 + 1
        
        // Horizontal pass
        for (y in 0 until targetH) {
            val rowOffset = y * targetW
            var rSum = 0
            var gSum = 0
            var bSum = 0
            
            // Initialize window sum
            for (dx in -r..r) {
                val px = dx.coerceIn(0, targetW - 1)
                val pixel = pixels[rowOffset + px]
                rSum += (pixel shr 16) and 0xFF
                gSum += (pixel shr 8) and 0xFF
                bSum += pixel and 0xFF
            }
            temp[rowOffset] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
            
            // Slide window
            for (x in 1 until targetW) {
                val oldPixelX = (x - 1 - r).coerceIn(0, targetW - 1)
                val newPixelX = (x + r).coerceIn(0, targetW - 1)
                
                val oldPixel = pixels[rowOffset + oldPixelX]
                val newPixel = pixels[rowOffset + newPixelX]
                
                rSum += ((newPixel shr 16) and 0xFF) - ((oldPixel shr 16) and 0xFF)
                gSum += ((newPixel shr 8) and 0xFF) - ((oldPixel shr 8) and 0xFF)
                bSum += (newPixel and 0xFF) - (oldPixel and 0xFF)
                
                temp[rowOffset + x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
            }
        }
        
        // Vertical pass
        for (x in 0 until targetW) {
            var rSum = 0
            var gSum = 0
            var bSum = 0
            
            // Initialize window sum
            for (dy in -r..r) {
                val py = dy.coerceIn(0, targetH - 1)
                val pixel = temp[py * targetW + x]
                rSum += (pixel shr 16) and 0xFF
                gSum += (pixel shr 8) and 0xFF
                bSum += pixel and 0xFF
            }
            pixels[x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
            
            // Slide window
            for (y in 1 until targetH) {
                val oldPixelY = (y - 1 - r).coerceIn(0, targetH - 1)
                val newPixelY = (y + r).coerceIn(0, targetH - 1)
                
                val oldPixel = temp[oldPixelY * targetW + x]
                val newPixel = temp[newPixelY * targetW + x]
                
                rSum += ((newPixel shr 16) and 0xFF) - ((oldPixel shr 16) and 0xFF)
                gSum += ((newPixel shr 8) and 0xFF) - ((oldPixel shr 8) and 0xFF)
                bSum += (newPixel and 0xFF) - (oldPixel and 0xFF)
                
                pixels[y * targetW + x] = (0xFF shl 24) or ((rSum / windowSize) shl 16) or ((gSum / windowSize) shl 8) or (bSum / windowSize)
            }
        }
        
        val tinyBlurred = android.graphics.Bitmap.createBitmap(targetW, targetH, android.graphics.Bitmap.Config.ARGB_8888)
        tinyBlurred.setPixels(pixels, 0, targetW, 0, 0, targetW, targetH)
        
        val upscaled = android.graphics.Bitmap.createScaledBitmap(tinyBlurred, w, h, true)
        tinyBlurred.recycle()
        return upscaled
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
        try {
            imageSegmenter?.close()
        } catch (e: Exception) {
            // Ignore
        }
    }

    companion object {
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
            arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
        } else {
            arrayOf(Manifest.permission.CAMERA)
        }
    }
}
