package com.recordatorios.app

import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.media.MediaRecorder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.speech.RecognizerIntent
import android.text.Editable
import android.text.TextWatcher
import android.graphics.Color
import android.util.Base64
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import android.widget.ImageButton
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.google.android.material.tabs.TabLayout
import com.google.gson.Gson
import com.recordatorios.app.databinding.ActivityMainBinding
import com.recordatorios.app.databinding.DialogNoteEditorBinding
import com.recordatorios.app.databinding.DialogCategoryManagerBinding
import com.recordatorios.app.databinding.DialogCategoryEditorBinding
import android.content.Context
import java.io.File
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var notesManager: NotesManager
    private lateinit var categoryManager: CategoryManager
    private lateinit var securityManager: SecurityManager
    private lateinit var adapter: NoteAdapter
    private lateinit var monthNotesAdapter: NoteAdapter

    private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
    private val timeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())

    private var editingNote: Note? = null
    private var selectedDate: Long? = null
    private var selectedHour = 0
    private var selectedMinute = 0
    private var currentNotes = listOf<Note>()
    private var selectedCategoryFilter: Set<String> = emptySet()
    private var isUpdatingFilter = false
    private var advancedCategoryFilter: Set<String> = emptySet()
    private var filterDateStart: Long? = null
    private var filterDateEnd: Long? = null
    private var mediaRecorder: MediaRecorder? = null
    private var audioFilePath: String? = null
    private var currentEditorBinding: DialogNoteEditorBinding? = null
    private var currentPhotoPath: String = ""
    private var calendarCurrentMonth: Calendar = Calendar.getInstance()
    private var swipeTouchStartX = 0f
    private var swipeTouchStartY = 0f
    private var biometricEnabled = false
    private var isUnlocked = false
    private var suppressSearch = false
    private val calendarTapStates = mutableMapOf<String, Int>()

    private val speechLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == RESULT_OK) {
            val spokenText = result.data
                ?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
                ?.getOrNull(0) ?: ""
            currentEditorBinding?.etContent?.apply {
                val existing = text?.toString() ?: ""
                val sep = if (existing.isBlank()) "" else "\n\n"
                setText(existing + sep + spokenText)
                setSelection(length())
            }
        }
        currentEditorBinding?.btnVoiceRecord?.isEnabled = true
        currentEditorBinding?.textVoiceStatus?.text = getString(R.string.voice_note)
    }

    private val voiceAddLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == RESULT_OK) {
            val spokenText = result.data
                ?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
                ?.getOrNull(0) ?: ""
            if (spokenText.isNotBlank()) {
                val categories = categoryManager.getAllCategories().map { it.name }
                val parsed = VoiceNoteParser.parse(spokenText, categories)
                showNoteEditor(null, parsed)
            }
        }
    }

    private val photoPickerLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        if (uri != null) {
            val savedPath = PhotoUtils.copyToInternalStorage(this, uri)
            if (savedPath != null) {
                currentPhotoPath = savedPath
                currentEditorBinding?.let { updatePhotoPreview(it, savedPath) }
            }
        }
    }

    private val backgroundPickerLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        if (uri != null) {
            val savedPath = PhotoUtils.copyToInternalStorage(this, uri)
            if (savedPath != null) {
                val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
                prefs.edit().putString("background_path", savedPath).apply()
                applyBackgroundImage()
            }
        }
    }

    private val exportLauncher = registerForActivityResult(
        ActivityResultContracts.CreateDocument("application/json")
    ) { uri: Uri? ->
        if (uri != null) {
            exportData(uri)
        }
    }

    private val importLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        if (uri != null) {
            importData(uri)
        }
    }

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

        securityManager = SecurityManager.getInstance(this)
        notesManager = NotesManager.getInstance(this)
        notesManager.setSecurityManager(securityManager)
        categoryManager = CategoryManager.getInstance(this)
        categoryManager.setSecurityManager(securityManager)

        val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
        biometricEnabled = prefs.getBoolean("biometric_enabled", false)

        if (biometricEnabled) {
            securityManager.authenticateBiometric(this) { success ->
                if (success) {
                    securityManager.migrateData()
                    categoryManager.reload()
                    initializeApp()
                } else {
                    finish()
                }
            }
        } else {
            initializeApp()
        }
    }

    private fun initializeApp() {
        migrateOldCategories()
        cleanTrash()

        requestNotificationPermission()

        setupRecyclerView()
        setupTabs()
        setupCategoryFilter()
        setupAdvancedFilter()
        setupSearch()
        setupFab()
        setupCalendar()
        setupSwipeGestures()
        setupSettings()
        applyBackgroundImage()
        loadNotes()
    }

    private fun migrateOldCategories() {
        val allNotes = notesManager.getAllNotes()
        val validIds = categoryManager.getAllCategories().map { it.id }.toSet()
        if (validIds.isEmpty()) return
        var changed = false
        for (note in allNotes) {
            if (note.category.isNotEmpty() && note.category !in validIds) {
                note.category = ""
                changed = true
            }
        }
        if (changed) notesManager.saveNotes(allNotes)
    }

    private fun setupRecyclerView() {
        adapter = NoteAdapter(
            onEdit = { note -> showNoteEditor(note) },
            onDelete = { note -> confirmDelete(note) },
            onToggleComplete = { note -> toggleComplete(note) },
            onRestore = { note -> restoreNote(note) },
            onPermanentDelete = { note -> confirmPermanentDelete(note) }
        )
        adapter.setCategories(categoryManager.getAllCategories())
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = adapter

        monthNotesAdapter = NoteAdapter(
            onEdit = { note -> showNoteEditor(note) },
            onDelete = { note -> confirmDelete(note) },
            onToggleComplete = { note -> toggleComplete(note) },
            onRestore = { note -> restoreNote(note) },
            onPermanentDelete = { note -> confirmPermanentDelete(note) }
        )
        monthNotesAdapter.setCategories(categoryManager.getAllCategories())
        binding.monthNotesList.layoutManager = LinearLayoutManager(this)
        binding.monthNotesList.adapter = monthNotesAdapter
    }

    private fun setupTabs() {
        val tabIcons = listOf(
            R.drawable.ic_sun,
            R.drawable.ic_clock,
            R.drawable.ic_calendar,
            R.drawable.ic_trash,
            R.drawable.ic_settings
        )
        for (i in 0 until binding.tabLayout.tabCount) {
            binding.tabLayout.getTabAt(i)?.setIcon(tabIcons[i])
        }

        binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                val position = binding.tabLayout.selectedTabPosition
                val isTodayTab = position == 0
                val isCalendarTab = position == 2
                val isDeletedTab = position == 3
                val isSettingsTab = position == 4

                binding.calendarContainer.visibility = if (isCalendarTab) View.VISIBLE else View.GONE
                binding.settingsContainer.visibility = if (isSettingsTab) View.VISIBLE else View.GONE
                binding.recyclerView.visibility = if (isCalendarTab || isSettingsTab) View.GONE else View.VISIBLE
                binding.emptyView.visibility = View.GONE
                suppressSearch = true
                binding.etSearch.text?.clear()
                val showSearch = !isCalendarTab && !isSettingsTab && !isTodayTab
                binding.searchLayout.visibility = if (showSearch) View.VISIBLE else View.GONE
                binding.btnFilter.visibility = if (showSearch) View.VISIBLE else View.GONE
                binding.chipGroupFilter.visibility = View.GONE
                binding.btnAdd.visibility = if (isDeletedTab || isCalendarTab || isSettingsTab) View.GONE else View.VISIBLE
                binding.btnVoiceAdd.visibility = if (isDeletedTab || isCalendarTab || isSettingsTab) View.GONE else View.VISIBLE

                adapter.setShowDeleted(isDeletedTab)

                when {
                    isCalendarTab -> refreshCalendar()
                    isSettingsTab -> updateSettingsSwitch()
                    else -> {
                        val animate = position == 0 || position == 1 || position == 3
                        if (animate) binding.recyclerView.itemAnimator = null
                        loadNotes(animate = animate)
                    }
                }
                suppressSearch = false
            }
            override fun onTabUnselected(tab: TabLayout.Tab?) {}
            override fun onTabReselected(tab: TabLayout.Tab?) {
                val position = binding.tabLayout.selectedTabPosition
                when (position) {
                    2 -> refreshCalendar()
                    0, 1, 3 -> {
                        binding.recyclerView.itemAnimator = null
                        loadNotes(animate = true)
                    }
                    else -> loadNotes()
                }
            }
        })
    }

    private fun setupCalendar() {
        // month navigation handled via swipe gestures
    }

    private fun refreshCalendar() {
        val cal = calendarCurrentMonth.clone() as Calendar
        val month = cal.get(Calendar.MONTH)
        val year = cal.get(Calendar.YEAR)

        val isDark = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES
        val calendarTextColor = if (isDark) Color.WHITE else Color.BLACK

        val monthNames = arrayOf("Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre")
        binding.textMonthYear.text = "${monthNames[month]} $year"
        binding.textMonthYear.setTextColor(calendarTextColor)

        val dayNamesView = binding.calendarDayNames
        if (dayNamesView.childCount == 0) {
            val dayNames = arrayOf("Dom", "Lun", "Mar", "Mi\u00e9", "Jue", "Vie", "S\u00e1b")
            for (name in dayNames) {
                val tv = TextView(this).apply {
                    text = name
                    gravity = Gravity.CENTER
                    textSize = 12f
                    setTextColor(calendarTextColor)
                    layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
                }
                dayNamesView.addView(tv)
            }
        }

        val allNotes = notesManager.getAllNotes()
            .filter { it.deletedAt == null && it.reminderDate != null }
        val calForDate = Calendar.getInstance()
        val colorsForDay = mutableMapOf<Int, MutableSet<Int>>()
        val categories = categoryManager.getAllCategories()
        for (note in allNotes) {
            calForDate.timeInMillis = note.reminderDate!!
            if (calForDate.get(Calendar.MONTH) == month && calForDate.get(Calendar.YEAR) == year) {
                val day = calForDate.get(Calendar.DAY_OF_MONTH)
                val color = categories.find { it.id == note.category }?.color ?: 0xFF9E9E9E.toInt()
                colorsForDay.getOrPut(day) { mutableSetOf() }.add(color)
            }
        }

        cal.set(Calendar.DAY_OF_MONTH, 1)
        val firstDayOfWeek = cal.get(Calendar.DAY_OF_WEEK) - 1
        val daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH)

        val today = Calendar.getInstance()
        val isCurrentMonth = today.get(Calendar.MONTH) == month && today.get(Calendar.YEAR) == year
        val todayDay = today.get(Calendar.DAY_OF_MONTH)

        val grid = binding.calendarDaysGrid
        grid.removeAllViews()

        val cellWidth = resources.displayMetrics.widthPixels / 7
        val cellHeight = (cellWidth * 0.8).toInt()
        val dotSize = (5 * resources.displayMetrics.density).toInt()
        val density = resources.displayMetrics.density

        var day = 1
        for (row in 0 until 6) {
            for (col in 0 until 7) {
                val index = row * 7 + col

                val cell = LinearLayout(this).apply {
                    orientation = LinearLayout.VERTICAL
                    gravity = Gravity.CENTER
                    layoutParams = ViewGroup.LayoutParams(cellWidth, cellHeight)
                }

                if (index < firstDayOfWeek || day > daysInMonth) {
                    grid.addView(cell)
                    continue
                }

                val dayNum = day
                val dayText = TextView(this).apply {
                    text = dayNum.toString()
                    textSize = 14f
                    gravity = Gravity.CENTER
                    setTextColor(calendarTextColor)
                }

                if (isCurrentMonth && dayNum == todayDay) {
                    val size = (36 * density).toInt()
                    dayText.layoutParams = ViewGroup.LayoutParams(size, size)
                    dayText.setBackgroundResource(R.drawable.circle_category)
                    dayText.background.setTint(0xFF2196F3.toInt())
                    dayText.setTextColor(Color.WHITE)
                }

                cell.addView(dayText)

                val dayColors = colorsForDay[dayNum]
                if (dayColors != null) {
                    val dotRow = LinearLayout(this).apply {
                        orientation = LinearLayout.HORIZONTAL
                        gravity = Gravity.CENTER
                        layoutParams = ViewGroup.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT
                        )
                    }
                    for (color in dayColors) {
                        val dot = View(this).apply {
                            layoutParams = ViewGroup.LayoutParams(dotSize, dotSize)
                            setBackgroundResource(R.drawable.circle_category)
                            background.setTint(color)
                        }
                        dotRow.addView(dot)
                    }
                    cell.addView(dotRow)
                }

                cell.setOnClickListener {
                    val key = "${year}-${month}-${dayNum}"
                    val count = (calendarTapStates[key] ?: 0) + 1
                    calendarTapStates[key] = count
                    when (count) {
                        1 -> {
                            Handler(Looper.getMainLooper()).postDelayed({
                                val finalCount = calendarTapStates[key] ?: 0
                                calendarTapStates.remove(key)
                                if (finalCount < 3) showNotesForDay(year, month, dayNum)
                            }, 500)
                        }
                        3 -> {
                            calendarTapStates.remove(key)
                            Calendar.getInstance().apply {
                                set(year, month, dayNum, get(Calendar.HOUR_OF_DAY), get(Calendar.MINUTE), 0)
                                set(Calendar.MILLISECOND, 0)
                            }.let { cal ->
                                showNoteEditor(null, prefillDate = cal.timeInMillis)
                            }
                        }
                    }
                }

                grid.addView(cell)
                day++
            }
        }

        val monthNotes = allNotes.filter { note ->
            calForDate.timeInMillis = note.reminderDate!!
            calForDate.get(Calendar.MONTH) == month && calForDate.get(Calendar.YEAR) == year
        }.sortedBy { it.reminderDate }

        binding.labelMonthNotes.text = "${monthNames[month]} — ${monthNotes.size} recordatorio${if (monthNotes.size != 1) "s" else ""}"
        binding.monthNotesList.itemAnimator = null
        monthNotesAdapter.submitList(monthNotes, animate = true)
    }

    private fun showNotesForDay(year: Int, month: Int, day: Int) {
        val targetCal = Calendar.getInstance().apply {
            set(year, month, day, 0, 0, 0)
            set(Calendar.MILLISECOND, 0)
        }
        val startOfDay = targetCal.timeInMillis
        val endOfDay = startOfDay + 86400000L

        val notesForDay = notesManager.getAllNotes().filter { note ->
            note.deletedAt == null && note.reminderDate != null &&
            note.reminderDate!! >= startOfDay && note.reminderDate!! < endOfDay
        }

        if (notesForDay.isEmpty()) return

        val items = notesForDay.map {
            val timeStr = timeFormat.format(Date(it.reminderDate!!))
            "${it.title.ifBlank { "Sin t\u00edtulo" }} - $timeStr"
        }

        MaterialAlertDialogBuilder(this)
            .setTitle("$day/${month + 1}/$year")
            .setItems(items.toTypedArray(), null)
            .setPositiveButton("Cerrar", null)
            .show()
    }

    private fun setupSwipeGestures() {
        // handled via dispatchTouchEvent
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        if (ev != null && ev.y > binding.tabLayout.bottom) {
            when (ev.action) {
                MotionEvent.ACTION_DOWN -> {
                    swipeTouchStartX = ev.x
                    swipeTouchStartY = ev.y
                }
                MotionEvent.ACTION_UP -> {
                    val diffX = ev.x - swipeTouchStartX
                    val diffY = ev.y - swipeTouchStartY
                    val absDiffX = Math.abs(diffX)
                    val absDiffY = Math.abs(diffY)
                    val current = binding.tabLayout.selectedTabPosition

                    if (absDiffY > 250 && absDiffY > absDiffX * 2.5f && current == 2) {
                        val gridLoc = IntArray(2)
                        binding.calendarDaysGrid.getLocationInWindow(gridLoc)
                        val gridBottom = gridLoc[1] + binding.calendarDaysGrid.height
                        if (Math.round(swipeTouchStartY) < gridBottom) {
                            if (diffY > 0) {
                                calendarCurrentMonth.add(Calendar.MONTH, -1)
                                animateMonthTransition(toPrev = true, onComplete = { refreshCalendar() }, vertical = true)
                            } else {
                                calendarCurrentMonth.add(Calendar.MONTH, 1)
                                animateMonthTransition(toPrev = false, onComplete = { refreshCalendar() }, vertical = true)
                            }
                            return true
                        }
                    }

                    if (absDiffX > 150 && absDiffX > absDiffY * 1.5f) {
                        if (diffX > 0 && current > 0) {
                            binding.tabLayout.getTabAt(current - 1)?.select()
                            return true
                        } else if (diffX < 0 && current < binding.tabLayout.tabCount - 1) {
                            binding.tabLayout.getTabAt(current + 1)?.select()
                            return true
                        }
                    }
                }
            }
        }
        return super.dispatchTouchEvent(ev)
    }

    private fun setupSearch() {
        binding.etSearch.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
            override fun afterTextChanged(s: Editable?) {
                if (!suppressSearch) applySearch(s?.toString() ?: "")
            }
        })
    }

    private fun setupFab() {
        binding.btnAdd.setOnClickListener { showNoteEditor(null) }

        binding.btnVoiceAdd.setOnClickListener {
            val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
                putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
                putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
                putExtra(RecognizerIntent.EXTRA_PROMPT, "Di lo que quieras recordar…")
            }
            try {
                voiceAddLauncher.launch(intent)
            } catch (e: Exception) {
                Toast.makeText(this, getString(R.string.voice_unavailable), Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun setupCategoryFilter() {
        binding.chipGroupFilter.removeAllViews()
        val categories = categoryManager.getAllCategories()
        val prefs = getSharedPreferences("filters", Context.MODE_PRIVATE)
        val savedFilters = prefs.getStringSet("category_filters", emptySet()) ?: emptySet()

        for (cat in categories) {
            val chip = Chip(this)
            chip.id = View.generateViewId()
            chip.text = cat.name
            chip.isCheckable = true
            chip.tag = cat.id
            val dot = AppCompatResources.getDrawable(this, R.drawable.circle_category)
            dot?.setTint(cat.color)
            chip.chipIcon = dot
            binding.chipGroupFilter.addView(chip)
        }

        if (savedFilters.isNotEmpty()) {
            isUpdatingFilter = true
            for (i in 0 until binding.chipGroupFilter.childCount) {
                val chip = binding.chipGroupFilter.getChildAt(i) as Chip
                if (chip.tag as? String in savedFilters) {
                    chip.isChecked = true
                }
            }
            selectedCategoryFilter = savedFilters
            isUpdatingFilter = false
            loadNotes()
        }

        binding.chipGroupFilter.setOnCheckedStateChangeListener { group, _ ->
            if (isUpdatingFilter) return@setOnCheckedStateChangeListener
            isUpdatingFilter = true

            selectedCategoryFilter = group.checkedChipIds.mapNotNull { id ->
                val chip = group.findViewById<Chip>(id)
                chip?.tag as? String
            }.toSet()

            prefs.edit().putStringSet("category_filters", selectedCategoryFilter).apply()

            isUpdatingFilter = false
            loadNotes()
        }
    }

    private fun setupAdvancedFilter() {
        binding.btnFilter.setOnClickListener { showAdvancedSearchDialog() }
        updateFilterButtonState()
    }

    private fun updateFilterButtonState() {
        val hasFilters = advancedCategoryFilter.isNotEmpty() || filterDateStart != null || filterDateEnd != null
        binding.btnFilter.imageAlpha = if (hasFilters) 255 else 128
    }

    private fun applyAdvancedFilters(notes: List<Note>): List<Note> {
        var filtered = notes
        if (advancedCategoryFilter.isNotEmpty()) {
            filtered = filtered.filter { it.category in advancedCategoryFilter }
        }
        val start = filterDateStart
        val end = filterDateEnd
        if (start != null || end != null) {
            filtered = filtered.filter { note ->
                val rd = note.reminderDate ?: return@filter true
                (start == null || rd >= start) &&
                (end == null || rd <= end)
            }
        }
        return filtered
    }

    private fun showAdvancedSearchDialog() {
        val categories = categoryManager.getAllCategories()
        val catIds = categories.map { it.id }.toTypedArray()

        var tempDateStart = filterDateStart
        var tempDateEnd = filterDateEnd

        val startStr = tempDateStart?.let { dateFormat.format(Date(it)) } ?: ""
        val endStr = tempDateEnd?.let { dateFormat.format(Date(it)) } ?: ""

        val layout = LinearLayout(this).apply {
            orientation = LinearLayout.VERTICAL
            setPadding(48, 24, 48, 24)
        }

        val catTitle = TextView(this).apply {
            text = "Categorías"
            textSize = 16f
            setTypeface(null, android.graphics.Typeface.BOLD)
            setPadding(0, 0, 0, 12)
            layout.addView(this)
        }

        val catChips = com.google.android.material.chip.ChipGroup(this).apply {
            isSingleSelection = false
            isSelectionRequired = false
            for (i in categories.indices) {
                val cat = categories[i]
                val chip = com.google.android.material.chip.Chip(this@MainActivity).apply {
                    id = View.generateViewId()
                    text = cat.name
                    isCheckable = true
                    tag = catIds[i]
                    isChecked = catIds[i] in advancedCategoryFilter
                    val dot = AppCompatResources.getDrawable(this@MainActivity, R.drawable.circle_category)
                    dot?.setTint(cat.color)
                    chipIcon = dot
                }
                addView(chip)
            }
            layoutParams = LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
            ).also { it.bottomMargin = 24 }
            layout.addView(this)
        }

        val dateTitle = TextView(this).apply {
            text = "Rango de fecha (recordatorio)"
            textSize = 16f
            setTypeface(null, android.graphics.Typeface.BOLD)
            setPadding(0, 0, 0, 12)
            layout.addView(this)
        }

        val dateRow = LinearLayout(this).apply {
            orientation = LinearLayout.HORIZONTAL
            gravity = android.view.Gravity.CENTER_VERTICAL
            layoutParams = LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
            )
            layout.addView(this)
        }

        val btnStart = com.google.android.material.button.MaterialButton(this).apply {
            text = startStr.ifEmpty { "Desde" }
            setOnClickListener {
                val picker = MaterialDatePicker.Builder.datePicker()
                    .setTitleText("Fecha inicio")
                    .setSelection(tempDateStart ?: MaterialDatePicker.todayInUtcMilliseconds())
                    .build()
                picker.addOnPositiveButtonClickListener { millis ->
                    tempDateStart = utcMillisToLocalDate(millis)
                    text = dateFormat.format(Date(tempDateStart!!))
                }
                picker.show(supportFragmentManager, "filter_start_date")
            }
            layoutParams = LinearLayout.LayoutParams(
                0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f
            ).also { it.setMargins(0, 0, 8, 0) }
            dateRow.addView(this)
        }

        val btnEnd = com.google.android.material.button.MaterialButton(this).apply {
            text = endStr.ifEmpty { "Hasta" }
            setOnClickListener {
                val picker = MaterialDatePicker.Builder.datePicker()
                    .setTitleText("Fecha fin")
                    .setSelection(tempDateEnd ?: MaterialDatePicker.todayInUtcMilliseconds())
                    .build()
                picker.addOnPositiveButtonClickListener { millis ->
                    tempDateEnd = utcMillisToLocalDate(millis)
                    text = dateFormat.format(Date(tempDateEnd!!))
                }
                picker.show(supportFragmentManager, "filter_end_date")
            }
            layoutParams = LinearLayout.LayoutParams(
                0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f
            ).also { it.setMargins(8, 0, 0, 0) }
            dateRow.addView(this)
        }

        MaterialAlertDialogBuilder(this)
            .setTitle("Búsqueda avanzada")
            .setView(layout)
            .setPositiveButton("Aplicar") { _, _ ->
                advancedCategoryFilter = catChips.checkedChipIds.mapNotNull { id ->
                    val chip = catChips.findViewById<com.google.android.material.chip.Chip>(id)
                    chip?.tag as? String
                }.toSet()
                filterDateStart = tempDateStart
                filterDateEnd = tempDateEnd
                updateFilterButtonState()
                loadNotes()
            }
            .setNegativeButton("Limpiar") { _, _ ->
                advancedCategoryFilter = emptySet()
                filterDateStart = null
                filterDateEnd = null
                updateFilterButtonState()
                loadNotes()
            }
            .setNeutralButton("Cancelar", null)
            .show()
    }

    private fun setupSettings() {
        val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
        biometricEnabled = prefs.getBoolean("biometric_enabled", false)
        binding.switchBiometric.isChecked = biometricEnabled

        binding.switchBiometric.setOnCheckedChangeListener { _, isChecked ->
            if (isChecked) {
                if (securityManager.isBiometricAvailable()) {
                    securityManager.authenticateBiometric(this) { success ->
                        if (success) {
                            securityManager.migrateData()
                            biometricEnabled = true
                            prefs.edit().putBoolean("biometric_enabled", true).apply()
                        } else {
                            binding.switchBiometric.isChecked = false
                        }
                    }
                } else {
                    binding.switchBiometric.isChecked = false
                }
            } else {
                biometricEnabled = false
                securityManager.lock()
                prefs.edit().putBoolean("biometric_enabled", false).apply()
            }
        }

        val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
        setThemeChip(themeMode)
        binding.chipGroupTheme.setOnCheckedStateChangeListener { _, checkedIds ->
            if (checkedIds.isNotEmpty()) {
                val mode = when (checkedIds[0]) {
                    binding.chipThemeLight.id -> AppCompatDelegate.MODE_NIGHT_NO
                    binding.chipThemeDark.id -> AppCompatDelegate.MODE_NIGHT_YES
                    else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
                }
                prefs.edit().putInt("theme_mode", mode).apply()
                AppCompatDelegate.setDefaultNightMode(mode)
            }
        }

        binding.btnPickBackground.setOnClickListener {
            backgroundPickerLauncher.launch("image/*")
        }
        binding.btnRemoveBackground.setOnClickListener {
            val prefs2 = getSharedPreferences("settings", Context.MODE_PRIVATE)
            prefs2.edit().remove("background_path").apply()
            binding.ivBackground.setImageDrawable(null)
            binding.ivBackground.visibility = View.GONE
            binding.backgroundScrim.visibility = View.GONE
        }

        binding.switchNotifImage.isChecked = prefs.getBoolean("notif_show_image", true)
        binding.switchNotifImage.setOnCheckedChangeListener { _, isChecked ->
            prefs.edit().putBoolean("notif_show_image", isChecked).apply()
        }

        val snoozeMinutes = prefs.getInt("snooze_minutes", 10)
        setSnoozeChip(snoozeMinutes)
        binding.chipGroupSnooze.setOnCheckedStateChangeListener { _, checkedIds ->
            if (checkedIds.isNotEmpty()) {
                val minutes = when (checkedIds[0]) {
                    binding.chipSnooze30Min.id -> 30
                    binding.chipSnooze1Hour.id -> 60
                    binding.chipSnooze2Hours.id -> 120
                    else -> 10
                }
                prefs.edit().putInt("snooze_minutes", minutes).apply()
            }
        }

        val trashDays = prefs.getInt("trash_days", 0)
        setTrashChip(trashDays)
        binding.chipGroupTrash.setOnCheckedStateChangeListener { _, checkedIds ->
            if (checkedIds.isNotEmpty()) {
                val days = when (checkedIds[0]) {
                    binding.chipTrash1Day.id -> 1
                    binding.chipTrash3Days.id -> 3
                    binding.chipTrash7Days.id -> 7
                    binding.chipTrash30Days.id -> 30
                    else -> 0
                }
                prefs.edit().putInt("trash_days", days).apply()
            }
        }

        binding.btnExport.setOnClickListener { exportLauncher.launch("recordatorios_backup_${System.currentTimeMillis()}.json") }
        binding.btnImport.setOnClickListener { importLauncher.launch("application/json") }

        binding.btnHelpGuide.setOnClickListener { showHelpGuide() }
    }

    private fun showHelpGuide() {
        val webView = android.webkit.WebView(this)
        webView.settings.javaScriptEnabled = false
        webView.settings.defaultTextEncodingName = "UTF-8"

        val markdown = assets.open("guide.md").bufferedReader().use { it.readText() }
        val html = """
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <style>
                    body {
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                        line-height: 1.6;
                        padding: 16px;
                        margin: 0;
                        color: #333;
                        background: #fafafa;
                    }
                    h1 { color: #1976d2; border-bottom: 2px solid #1976d2; padding-bottom: 8px; }
                    h2 { color: #424242; margin-top: 24px; }
                    h3 { color: #616161; }
                    hr { border: none; border-top: 1px solid #e0e0e0; margin: 24px 0; }
                    code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }
                    pre { background: #f5f5f5; padding: 12px; border-radius: 8px; overflow-x: auto; }
                    a { color: #1976d2; text-decoration: none; }
                    a:hover { text-decoration: underline; }
                    ul, ol { padding-left: 24px; }
                    li { margin: 4px 0; }
                    strong { color: #212121; }
                </style>
            </head>
            <body>
                ${markdownToHtml(markdown)}
            </body>
            </html>
        """.trimIndent()

        webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null)

        MaterialAlertDialogBuilder(this)
            .setTitle("Guía de Uso")
            .setView(webView)
            .setPositiveButton("Cerrar", null)
            .show()
    }

    private fun markdownToHtml(markdown: String): String {
        var html = markdown
        html = html.replace(Regex("^### (.+)$", RegexOption.MULTILINE), "<h3>$1</h3>")
        html = html.replace(Regex("^## (.+)$", RegexOption.MULTILINE), "<h2>$1</h2>")
        html = html.replace(Regex("^# (.+)$", RegexOption.MULTILINE), "<h1>$1</h1>")
        html = html.replace(Regex("^---$", RegexOption.MULTILINE), "<hr>")
        html = html.replace(Regex("\\*\\*(.+?)\\*\\*"), "<strong>$1</strong>")
        html = html.replace(Regex("\\*(.+?)\\*"), "<em>$1</em>")
        html = html.replace(Regex("\\[(.+?)\\]\\((.+?)\\)"), "<a href=\"$2\">$1</a>")
        html = html.replace(Regex("`(.+?)`"), "<code>$1</code>")
        html = html.replace(Regex("^- (.+)$", RegexOption.MULTILINE), "<li>$1</li>")
        html = html.replace(Regex("(<li>.*</li>\n?)+"), "<ul>$0</ul>")
        html = html.replace(Regex("^(\\d+)\\. (.+)$", RegexOption.MULTILINE), "<li>$2</li>")
        html = html.replace(Regex("\n\n"), "</p><p>")
        html = "<p>$html</p>"
        html = html.replace("<p><h", "<h").replace("</h1></p>", "</h1>").replace("</h2></p>", "</h2>").replace("</h3></p>", "</h3>")
        html = html.replace("<p><hr></p>", "<hr>")
        html = html.replace("<p><ul>", "<ul>").replace("</ul></p>", "</ul>")
        return html
    }

    private fun setSnoozeChip(minutes: Int) {
        val id = when (minutes) {
            30 -> binding.chipSnooze30Min.id
            60 -> binding.chipSnooze1Hour.id
            120 -> binding.chipSnooze2Hours.id
            else -> binding.chipSnooze10Min.id
        }
        binding.chipGroupSnooze.check(id)
    }

    private fun setTrashChip(days: Int) {
        val id = when (days) {
            1 -> binding.chipTrash1Day.id
            3 -> binding.chipTrash3Days.id
            7 -> binding.chipTrash7Days.id
            30 -> binding.chipTrash30Days.id
            else -> binding.chipTrashOff.id
        }
        binding.chipGroupTrash.check(id)
    }

    private fun setThemeChip(mode: Int) {
        val id = when (mode) {
            AppCompatDelegate.MODE_NIGHT_NO -> binding.chipThemeLight.id
            AppCompatDelegate.MODE_NIGHT_YES -> binding.chipThemeDark.id
            else -> binding.chipThemeSystem.id
        }
        binding.chipGroupTheme.check(id)
    }

    private fun applyBackgroundImage() {
        val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
        val path = prefs.getString("background_path", null)
        if (path.isNullOrBlank()) {
            binding.ivBackground.visibility = View.GONE
            binding.backgroundScrim.visibility = View.GONE
            return
        }
        val blurred = PhotoUtils.loadBlurredBitmap(this, path, maxWidth = 720, radius = 8f)
        if (blurred != null) {
            binding.ivBackground.setImageBitmap(blurred)
            binding.ivBackground.visibility = View.VISIBLE
            val isDark = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES
            binding.backgroundScrim.setBackgroundColor(if (isDark) 0x44000000.toInt() else 0x55FFFFFF.toInt())
            binding.backgroundScrim.visibility = View.VISIBLE
        } else {
            binding.ivBackground.visibility = View.GONE
            binding.backgroundScrim.visibility = View.GONE
        }
    }

    private fun updateSettingsSwitch() {
        val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
        biometricEnabled = prefs.getBoolean("biometric_enabled", false)
        binding.switchBiometric.isChecked = biometricEnabled
        val themeMode = prefs.getInt("theme_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
        setThemeChip(themeMode)
        binding.switchNotifImage.isChecked = prefs.getBoolean("notif_show_image", true)
        setSnoozeChip(prefs.getInt("snooze_minutes", 10))
        setTrashChip(prefs.getInt("trash_days", 0))
    }

    private fun cleanTrash() {
        val prefs = getSharedPreferences("settings", Context.MODE_PRIVATE)
        val trashDays = prefs.getInt("trash_days", 0)
        if (trashDays <= 0) return
        val cutoff = System.currentTimeMillis() - trashDays * 86400000L
        val allNotes = notesManager.getAllNotes()
        val expired = allNotes.filter { it.deletedAt != null && it.deletedAt!! < cutoff }.map { it.id }.toSet()
        if (expired.isNotEmpty()) {
            expired.forEach { notesManager.permanentlyDeleteNote(it) }
        }
    }

    private fun exportData(uri: Uri) {
        try {
            val allNotes = notesManager.getAllNotes()
            val photosMap = mutableMapOf<String, String>()
            for (note in allNotes) {
                if (note.photoPath.isNotBlank()) {
                    val file = File(note.photoPath)
                    if (file.exists()) {
                        val bytes = file.readBytes()
                        val base64 = Base64.encodeToString(bytes, Base64.DEFAULT)
                        photosMap[file.name] = base64
                        note.photoPath = file.name
                    } else {
                        note.photoPath = ""
                    }
                }
            }
            val gson = Gson()
            val notesJson = gson.toJson(allNotes)
            val backupData = BackupData(
                notes = notesJson,
                categories = categoryManager.exportToJson(),
                photos = photosMap
            )
            val json = gson.toJson(backupData)
            contentResolver.openOutputStream(uri)?.use { outputStream ->
                OutputStreamWriter(outputStream).use { writer ->
                    writer.write(json)
                }
            }
            Toast.makeText(this, "Datos exportados correctamente", Toast.LENGTH_LONG).show()
        } catch (e: Exception) {
            Toast.makeText(this, "Error al exportar: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }

    private fun importData(uri: Uri) {
        try {
            contentResolver.openInputStream(uri)?.use { inputStream ->
                InputStreamReader(inputStream).use { reader ->
                    val json = reader.readText()
                    val gson = Gson()
                    val backupData = gson.fromJson(json, BackupData::class.java)
                    
                    if (backupData != null) {
                        notesManager.importFromJson(backupData.notes)
                        categoryManager.importFromJson(backupData.categories, merge = false)
                        val photosMap = backupData.photos
                        val allNotes = notesManager.getAllNotes()
                        for (note in allNotes) {
                            if (note.photoPath.isNotBlank()) {
                                val filename = File(note.photoPath).name
                                val base64 = photosMap[filename]
                                if (base64 != null) {
                                    try {
                                        val dir = File(filesDir, "photos")
                                        dir.mkdirs()
                                        val file = File(dir, filename)
                                        file.writeBytes(Base64.decode(base64, Base64.DEFAULT))
                                        note.photoPath = file.absolutePath
                                    } catch (_: Exception) {
                                        note.photoPath = ""
                                    }
                                } else {
                                    note.photoPath = ""
                                }
                            }
                        }
                        notesManager.saveNotes(allNotes)
                        adapter.setCategories(categoryManager.getAllCategories())
                        monthNotesAdapter.setCategories(categoryManager.getAllCategories())
                        setupCategoryFilter()
                        migrateOldCategories()
                        Toast.makeText(this, "Datos importados correctamente", Toast.LENGTH_LONG).show()
                        loadNotes()
                    } else {
                        Toast.makeText(this, "Archivo de backup inválido", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        } catch (e: Exception) {
            Toast.makeText(this, "Error al importar: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }

    private data class BackupData(
        val notes: String = "",
        val categories: String = "",
        val photos: Map<String, String> = emptyMap()
    )

    private fun loadNotes(animate: Boolean = false) {
        val position = binding.tabLayout.selectedTabPosition
        if (position == 2 || position == 4) return

        val allNotes = notesManager.getAllNotes()
        val isDeletedTab = position == 3
        val isTodayTab = position == 0

        adapter.setShowDeleted(isDeletedTab)

        currentNotes = when (position) {
            0 -> {
                filterToday(allNotes.filter { !it.isCompleted && it.deletedAt == null })
            }
            1 -> {
                val pending = filterPendingReminders(allNotes.filter { !it.isCompleted && it.deletedAt == null })
                applyAdvancedFilters(pending)
            }
            3 -> {
                val deleted = allNotes.filter { it.deletedAt != null }.sortedByDescending { it.deletedAt }
                applyAdvancedFilters(deleted)
            }
            else -> allNotes
        }

        val usesAdvancedFilter = position == 1 || position == 3
        if (!isTodayTab && !usesAdvancedFilter && selectedCategoryFilter.isNotEmpty()) {
            currentNotes = currentNotes.filter { it.category in selectedCategoryFilter }
        }

        applySearch(binding.etSearch.text?.toString() ?: "", animate = animate)
        TodayWidgetProvider.updateAllWidgets(this)
    }

    private fun applySearch(query: String, animate: Boolean = false) {
        val position = binding.tabLayout.selectedTabPosition
        if (position == 2 || position == 4) return

        val filtered = if (query.isBlank()) {
            currentNotes
        } else {
            val q = query.lowercase(Locale.ROOT)
            currentNotes.filter { note ->
                note.title.lowercase(Locale.ROOT).contains(q) ||
                note.content.lowercase(Locale.ROOT).contains(q)
            }
        }

        adapter.submitList(filtered, isTodayTab = position == 0, animate = animate)

        val emptyMsg = when {
            query.isNotBlank() && filtered.isEmpty() -> "Sin resultados para \"$query\""
            position == 0 -> getString(R.string.no_notes_today)
            position == 1 -> getString(R.string.no_reminders_pending)
            position == 3 -> "No hay notas eliminadas"
            else -> getString(R.string.no_notes)
        }
        binding.emptyView.text = emptyMsg
        binding.emptyView.visibility = if (filtered.isEmpty()) View.VISIBLE else View.GONE
    }

    private fun getTodayRange(): Pair<Long, Long> {
        val start = Calendar.getInstance().apply {
            set(Calendar.HOUR_OF_DAY, 0)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }.timeInMillis
        return start to (start + 86400000L)
    }

    private fun filterToday(notes: List<Note>): List<Note> {
        val (todayStart, todayEnd) = getTodayRange()
        val filtered = notes.filter { note ->
            note.reminderDate != null && note.reminderDate!! < todayEnd
        }
        return filtered.sortedByDescending { note ->
            if (note.reminderDate!! < todayStart) 1 else 0
        }
    }

    private fun filterPendingReminders(notes: List<Note>): List<Note> {
        val (_, todayEnd) = getTodayRange()
        return notes.filter { it.reminderDate == null || it.reminderDate!! >= todayEnd }
    }

    private fun toggleComplete(note: Note) {
        if (note.recurrence != RecurrenceType.NONE && note.reminderDate != null) {
            val nextDate = calculateNextRecurrence(note.reminderDate!!, note.recurrence)
            note.reminderDate = nextDate
            note.isCompleted = false
            notesManager.updateNote(note)
            ReminderScheduler.cancelReminder(this, note)
            ReminderScheduler.scheduleReminder(this, note)
        } else {
            note.isCompleted = true
            note.deletedAt = System.currentTimeMillis()
            notesManager.updateNote(note)
        }
        loadNotes()
    }

    private fun calculateNextRecurrence(currentTime: Long, recurrence: RecurrenceType): Long {
        val cal = Calendar.getInstance().apply { timeInMillis = currentTime }
        return when (recurrence) {
            RecurrenceType.DAILY -> cal.apply { add(Calendar.DAY_OF_YEAR, 1) }.timeInMillis
            RecurrenceType.WEEKLY -> cal.apply { add(Calendar.WEEK_OF_YEAR, 1) }.timeInMillis
            RecurrenceType.MONTHLY -> cal.apply { add(Calendar.MONTH, 1) }.timeInMillis
            RecurrenceType.YEARLY -> cal.apply { add(Calendar.YEAR, 1) }.timeInMillis
            else -> currentTime
        }
    }

    private fun confirmDelete(note: Note) {
        MaterialAlertDialogBuilder(this)
            .setTitle(R.string.delete_note)
            .setMessage(R.string.delete_confirm)
            .setPositiveButton(R.string.delete) { _, _ ->
                ReminderScheduler.cancelReminder(this, note)
                notesManager.softDeleteNote(note.id)
                loadNotes()
            }
            .setNegativeButton(R.string.cancel, null)
            .show()
    }

    private fun confirmPermanentDelete(note: Note) {
        MaterialAlertDialogBuilder(this)
            .setTitle("Eliminar permanentemente")
            .setMessage("Esta nota se eliminar\u00e1 para siempre.")
            .setPositiveButton("Eliminar") { _, _ ->
                notesManager.permanentlyDeleteNote(note.id)
                loadNotes()
            }
            .setNegativeButton(R.string.cancel, null)
            .show()
    }

    private fun restoreNote(note: Note) {
        notesManager.restoreNote(note.id)
        binding.tabLayout.getTabAt(0)?.select()
        binding.recyclerView.adapter = adapter
        Toast.makeText(this, "Nota restaurada", Toast.LENGTH_SHORT).show()
    }

    private fun startVoiceRecording(dialogBinding: DialogNoteEditorBinding) {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO)
            != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(android.Manifest.permission.RECORD_AUDIO),
                RECORD_AUDIO_REQUEST_CODE
            )
            return
        }

        if (mediaRecorder != null) {
            stopRecording(dialogBinding)
            return
        }

        try {
            val dir = File(filesDir, "voice_notes")
            dir.mkdirs()
            audioFilePath = File(dir, "voice_${System.currentTimeMillis()}.3gp").absolutePath

            mediaRecorder = if (Build.VERSION.SDK_INT >= 31) {
                MediaRecorder(this)
            } else {
                @Suppress("DEPRECATION") MediaRecorder()
            }.apply {
                setAudioSource(MediaRecorder.AudioSource.MIC)
                setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
                setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
                setOutputFile(audioFilePath)
                prepare()
                start()
            }

            dialogBinding.btnVoiceRecord.setColorFilter(getColor(android.R.color.holo_red_dark))
            dialogBinding.textVoiceStatus.text = getString(R.string.voice_recording)
            dialogBinding.btnVoiceRecord.isEnabled = true
        } catch (e: Exception) {
            Toast.makeText(this, "Error al grabar: ${e.message}", Toast.LENGTH_SHORT).show()
            mediaRecorder = null
        }
    }

    private fun stopRecording(dialogBinding: DialogNoteEditorBinding) {
        try {
            mediaRecorder?.apply {
                stop()
                release()
            }
        } catch (_: Exception) {}
        mediaRecorder = null

        dialogBinding.btnVoiceRecord.clearColorFilter()
        dialogBinding.textVoiceStatus.text = getString(R.string.voice_note)

        val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
            putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
            putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
            putExtra(RecognizerIntent.EXTRA_PROMPT, "Habla ahora...")
        }
        try {
            speechLauncher.launch(intent)
        } catch (e: Exception) {
            Toast.makeText(this, getString(R.string.voice_unavailable), Toast.LENGTH_SHORT).show()
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == RECORD_AUDIO_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                currentEditorBinding?.let { startVoiceRecording(it) }
            } else {
                Toast.makeText(this, "Permiso de micrófono requerido", Toast.LENGTH_SHORT).show()
            }
        }
    }

    private fun showNoteEditor(existingNote: Note?, voiceNote: ParsedVoiceNote? = null, prefillDate: Long? = null) {
        editingNote = existingNote
        val dialogBinding = DialogNoteEditorBinding.inflate(layoutInflater)
        currentEditorBinding = dialogBinding

        resetEditorState()
        populateCategoryChips(dialogBinding, existingNote?.category)
        currentPhotoPath = existingNote?.photoPath ?: ""

        if (existingNote == null && prefillDate != null) {
            dialogBinding.switchReminder.isChecked = true
            selectedDate = prefillDate
            val cal = Calendar.getInstance().apply { timeInMillis = prefillDate }
            selectedHour = cal.get(Calendar.HOUR_OF_DAY)
            selectedMinute = cal.get(Calendar.MINUTE)
            updateDateTimeFields(dialogBinding)
            showReminderFields(dialogBinding, true)
        }

        if (existingNote != null) {
            dialogBinding.etTitle.setText(existingNote.title)
            dialogBinding.etContent.setText(existingNote.content)
            if (existingNote.reminderDate != null) {
                dialogBinding.switchReminder.isChecked = true
                selectedDate = existingNote.reminderDate
                val cal = Calendar.getInstance().apply { timeInMillis = existingNote.reminderDate!! }
                selectedHour = cal.get(Calendar.HOUR_OF_DAY)
                selectedMinute = cal.get(Calendar.MINUTE)
                updateDateTimeFields(dialogBinding)
                showReminderFields(dialogBinding, true)
                setRecurrenceRadio(dialogBinding, existingNote.recurrence)
            }
        } else if (voiceNote != null) {
            dialogBinding.etTitle.setText(voiceNote.title)
            dialogBinding.etContent.setText(voiceNote.content)
            if (voiceNote.reminderDate != null) {
                dialogBinding.switchReminder.isChecked = true
                selectedDate = voiceNote.reminderDate
                val cal = Calendar.getInstance().apply { timeInMillis = voiceNote.reminderDate }
                selectedHour = cal.get(Calendar.HOUR_OF_DAY)
                selectedMinute = cal.get(Calendar.MINUTE)
                updateDateTimeFields(dialogBinding)
                showReminderFields(dialogBinding, true)
            }
            if (voiceNote.categoryName != null) {
                val matchingCategory = categoryManager.getAllCategories().find { 
                    it.name.equals(voiceNote.categoryName, ignoreCase = true) 
                }
                if (matchingCategory != null) {
                    selectCategoryChip(dialogBinding, matchingCategory.id)
                }
            }
            if (voiceNote.recurrence != RecurrenceType.NONE) {
                setRecurrenceRadio(dialogBinding, voiceNote.recurrence)
            }
        }

        if (currentPhotoPath.isNotBlank()) {
            updatePhotoPreview(dialogBinding, currentPhotoPath)
        }

        dialogBinding.btnAddPhoto.setOnClickListener {
            photoPickerLauncher.launch("image/*")
        }

        dialogBinding.btnRemovePhoto.setOnClickListener {
            currentPhotoPath = ""
            dialogBinding.imgPhotoPreview.visibility = View.GONE
            dialogBinding.btnRemovePhoto.visibility = View.GONE
            dialogBinding.textPhotoStatus.text = getString(R.string.photo_add)
        }

        dialogBinding.btnManageCategories.setOnClickListener { showCategoryManager(dialogBinding) }

        val isSpeechAvailable = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
            .let { it.resolveActivity(packageManager) != null }
        if (!isSpeechAvailable) {
            dialogBinding.voiceNoteRow.visibility = View.GONE
        } else {
            dialogBinding.btnVoiceRecord.setOnClickListener {
                startVoiceRecording(dialogBinding)
            }
        }

        dialogBinding.switchReminder.setOnCheckedChangeListener { _, isChecked ->
            showReminderFields(dialogBinding, isChecked)
            if (!isChecked) {
                selectedDate = null
                dialogBinding.etDate.setText("")
                dialogBinding.etTime.setText("")
            } else if (selectedDate == null) {
                val cal = Calendar.getInstance()
                selectedDate = cal.timeInMillis
                selectedHour = cal.get(Calendar.HOUR_OF_DAY)
                selectedMinute = cal.get(Calendar.MINUTE)
                updateDateTimeFields(dialogBinding)
            }
        }

        dialogBinding.etDate.setOnClickListener {
            val picker = MaterialDatePicker.Builder.datePicker()
                .setTitleText(R.string.select_date)
                .setSelection(localDateToUtcMillis(selectedDate ?: System.currentTimeMillis()))
                .build()

            picker.addOnPositiveButtonClickListener { millis ->
                selectedDate = utcMillisToLocalDate(millis)
                updateDateTimeFields(dialogBinding)
            }

            picker.show(supportFragmentManager, "date_picker")
        }

        dialogBinding.etTime.setOnClickListener {
            val now = Calendar.getInstance()
            val hour = if (selectedDate != null) {
                Calendar.getInstance().apply { timeInMillis = selectedDate!! }
                    .get(Calendar.HOUR_OF_DAY)
            } else now.get(Calendar.HOUR_OF_DAY)
            val minute = if (selectedDate != null) {
                Calendar.getInstance().apply { timeInMillis = selectedDate!! }
                    .get(Calendar.MINUTE)
            } else now.get(Calendar.MINUTE)

            val picker = MaterialTimePicker.Builder()
                .setTitleText(R.string.select_time)
                .setHour(hour)
                .setMinute(minute)
                .setTimeFormat(TimeFormat.CLOCK_24H)
                .build()

            picker.addOnPositiveButtonClickListener {
                selectedHour = picker.hour
                selectedMinute = picker.minute
                if (selectedDate != null) {
                    val cal = Calendar.getInstance().apply { timeInMillis = selectedDate!! }
                    cal.set(Calendar.HOUR_OF_DAY, selectedHour)
                    cal.set(Calendar.MINUTE, selectedMinute)
                    cal.set(Calendar.SECOND, 0)
                    cal.set(Calendar.MILLISECOND, 0)
                    selectedDate = cal.timeInMillis
                }
                updateDateTimeFields(dialogBinding)
            }

            picker.show(supportFragmentManager, "time_picker")
        }

        val dialog = MaterialAlertDialogBuilder(this)
            .setTitle(if (existingNote != null) R.string.edit_note else R.string.add_note)
            .setView(dialogBinding.root)
            .setPositiveButton(R.string.save, null)
            .setNegativeButton(R.string.cancel, null)
            .show()

        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
            val title = dialogBinding.etTitle.text.toString().trim()
            val content = dialogBinding.etContent.text.toString().trim()

            if (title.isEmpty() && content.isEmpty()) {
                Toast.makeText(this, "Escribe un t\u00edtulo o contenido", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val reminderTime = if (dialogBinding.switchReminder.isChecked && selectedDate != null) {
                val cal = Calendar.getInstance().apply { timeInMillis = selectedDate!! }
                cal.set(Calendar.HOUR_OF_DAY, selectedHour)
                cal.set(Calendar.MINUTE, selectedMinute)
                cal.set(Calendar.SECOND, 0)
                cal.set(Calendar.MILLISECOND, 0)
                cal.timeInMillis
            } else null

            val recurrence = if (dialogBinding.switchReminder.isChecked) {
                getSelectedRecurrence(dialogBinding)
            } else RecurrenceType.NONE

            val categoryId = getSelectedCategoryId(dialogBinding)

            if (editingNote != null) {
                editingNote!!.title = title
                editingNote!!.content = content
                editingNote!!.reminderDate = reminderTime
                editingNote!!.recurrence = recurrence
                editingNote!!.category = categoryId
                editingNote!!.photoPath = currentPhotoPath
                notesManager.updateNote(editingNote!!)

                ReminderScheduler.cancelReminder(this, editingNote!!)
                if (reminderTime != null) {
                    ReminderScheduler.scheduleReminder(this, editingNote!!)
                }
            } else {
                val note = Note(
                    title = title,
                    content = content,
                    reminderDate = reminderTime,
                    recurrence = recurrence,
                    category = categoryId,
                    photoPath = currentPhotoPath
                )
                notesManager.addNote(note)
                if (reminderTime != null) {
                    ReminderScheduler.scheduleReminder(this, note)
                }
            }

            dialog.dismiss()
            loadNotes()
        }
    }

    private fun populateCategoryChips(dialogBinding: DialogNoteEditorBinding, selectedCategoryId: String?) {
        dialogBinding.chipGroupCategory.removeAllViews()
        val categories = categoryManager.getAllCategories()

        val noneChip = Chip(this)
        noneChip.id = View.generateViewId()
        noneChip.text = getString(R.string.category_none)
        noneChip.isCheckable = true
        noneChip.tag = ""
        dialogBinding.chipGroupCategory.addView(noneChip)
        if (selectedCategoryId.isNullOrEmpty()) noneChip.isChecked = true

        for (cat in categories) {
            val chip = Chip(this)
            chip.id = View.generateViewId()
            chip.text = cat.name
            chip.isCheckable = true
            chip.tag = cat.id
            val dot = AppCompatResources.getDrawable(this, R.drawable.circle_category)
            dot?.setTint(cat.color)
            chip.chipIcon = dot
            dialogBinding.chipGroupCategory.addView(chip)
            if (cat.id == selectedCategoryId) chip.isChecked = true
        }
    }

    private fun selectCategoryChip(dialogBinding: DialogNoteEditorBinding, categoryId: String) {
        for (i in 0 until dialogBinding.chipGroupCategory.childCount) {
            val chip = dialogBinding.chipGroupCategory.getChildAt(i) as? Chip
            if (chip?.tag as? String == categoryId) {
                chip.isChecked = true
                break
            }
        }
    }

    private fun showCategoryManager(dialogBinding: DialogNoteEditorBinding) {
        val managerBinding = DialogCategoryManagerBinding.inflate(layoutInflater)
        refreshCategoryList(managerBinding)

        managerBinding.btnAddCategory.setOnClickListener {
            showCategoryEditor(null) {
                refreshCategoryList(managerBinding)
                populateCategoryChips(dialogBinding, null)
            }
        }

        MaterialAlertDialogBuilder(this)
            .setTitle(R.string.manage_categories)
            .setView(managerBinding.root)
            .setPositiveButton("Cerrar", null)
            .show()
    }

    private fun refreshCategoryList(managerBinding: DialogCategoryManagerBinding) {
        managerBinding.categoryList.removeAllViews()
        val categories = categoryManager.getAllCategories()
        for (cat in categories) {
            val chip = Chip(this)
            chip.text = cat.name
            chip.isCheckable = false
            chip.isClickable = false
            val dot = AppCompatResources.getDrawable(this, R.drawable.circle_category)
            dot?.setTint(cat.color)
            chip.chipIcon = dot

            val editBtn = ImageButton(this).apply {
                setImageResource(R.drawable.ic_pencil)
                contentDescription = "Editar"
                scaleType = ImageView.ScaleType.CENTER_INSIDE
                background = null
                setPadding(8, 8, 8, 8)
                layoutParams = LinearLayout.LayoutParams(
                    (40 * resources.displayMetrics.density).toInt(),
                    (40 * resources.displayMetrics.density).toInt()
                )
                setColorFilter(android.graphics.Color.GRAY)
                setOnClickListener {
                    showCategoryEditor(cat) {
                        refreshCategoryList(managerBinding)
                    }
                }
            }

            val deleteBtn = ImageButton(this).apply {
                setImageResource(R.drawable.ic_trash)
                contentDescription = "Eliminar"
                scaleType = ImageView.ScaleType.CENTER_INSIDE
                background = null
                setPadding(8, 8, 8, 8)
                layoutParams = LinearLayout.LayoutParams(
                    (40 * resources.displayMetrics.density).toInt(),
                    (40 * resources.displayMetrics.density).toInt()
                )
                setColorFilter(android.graphics.Color.GRAY)
                setOnClickListener {
                    MaterialAlertDialogBuilder(this@MainActivity)
                        .setTitle("Eliminar categor\u00eda")
                        .setMessage(getString(R.string.category_delete_confirm))
                        .setPositiveButton("Eliminar") { _, _ ->
                            val notes = notesManager.getAllNotes()
                            for (note in notes) {
                                if (note.category == cat.id) note.category = ""
                            }
                            notesManager.saveNotes(notes)
                            categoryManager.deleteCategory(cat.id)
                            selectedCategoryFilter = selectedCategoryFilter.filter { it in categoryManager.getAllCategories().map { c -> c.id } }.toSet()
                            getSharedPreferences("filters", Context.MODE_PRIVATE).edit()
                                .putStringSet("category_filters", selectedCategoryFilter).apply()
                            refreshCategoryList(managerBinding)
                            adapter.setCategories(categoryManager.getAllCategories())
                            monthNotesAdapter.setCategories(categoryManager.getAllCategories())
                            setupCategoryFilter()
                            loadNotes()
                        }
                        .setNegativeButton(R.string.cancel, null)
                        .show()
                }
            }

            val row = LinearLayout(this).apply {
                orientation = LinearLayout.HORIZONTAL
                gravity = Gravity.CENTER_VERTICAL
                setPadding(0, 4, 0, 4)
                addView(chip, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f))
                addView(editBtn)
                addView(deleteBtn)
            }
            managerBinding.categoryList.addView(row)
        }
    }

    private fun showCategoryEditor(existing: Category?, onDone: () -> Unit) {
        val editorBinding = DialogCategoryEditorBinding.inflate(layoutInflater)
        var selectedColor = existing?.color ?: Category.DEFAULT_COLORS[0]

        if (existing != null) {
            editorBinding.etCategoryName.setText(existing.name)
        }

        val swatchesContainer = editorBinding.colorSwatches
        val swatchSize = (40 * resources.displayMetrics.density).toInt()
        val margin = (4 * resources.displayMetrics.density).toInt()
        val cols = 6

        for (i in Category.DEFAULT_COLORS.indices) {
            if (i % cols == 0) {
                val row = LinearLayout(this).apply {
                    orientation = LinearLayout.HORIZONTAL
                    layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT
                    )
                }
                swatchesContainer.addView(row)
            }
            val color = Category.DEFAULT_COLORS[i]
            val swatch = View(this)
            swatch.layoutParams = ViewGroup.MarginLayoutParams(swatchSize, swatchSize)
            (swatch.layoutParams as ViewGroup.MarginLayoutParams).setMargins(margin, margin, margin, margin)
            swatch.background = createColorSwatchDrawable(color, color == selectedColor)
            swatch.setOnClickListener {
                selectedColor = color
                for (r in 0 until swatchesContainer.childCount) {
                    val row = swatchesContainer.getChildAt(r) as? LinearLayout ?: continue
                    for (c in 0 until row.childCount) {
                        row.getChildAt(c).background = createColorSwatchDrawable(
                            Category.DEFAULT_COLORS[(r * cols + c)],
                            false
                        )
                    }
                }
                swatch.background = createColorSwatchDrawable(color, true)
            }
            val lastRow = swatchesContainer.getChildAt(swatchesContainer.childCount - 1) as? LinearLayout
            lastRow?.addView(swatch)
        }

        val dialog = MaterialAlertDialogBuilder(this)
            .setTitle(if (existing != null) R.string.edit_category else R.string.new_category)
            .setView(editorBinding.root)
            .setPositiveButton(R.string.save, null)
            .setNegativeButton(R.string.cancel, null)
            .show()

        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
            val name = editorBinding.etCategoryName.text.toString().trim()
            if (name.isEmpty()) {
                Toast.makeText(this, "Escribe un nombre", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            if (existing != null) {
                categoryManager.updateCategory(existing.id, name, selectedColor)
            } else {
                categoryManager.addCategory(Category(name = name, color = selectedColor))
            }
            dialog.dismiss()
            adapter.setCategories(categoryManager.getAllCategories())
            monthNotesAdapter.setCategories(categoryManager.getAllCategories())
            setupCategoryFilter()
            onDone()
        }
    }

    private fun createColorSwatchDrawable(color: Int, isSelected: Boolean): android.graphics.drawable.GradientDrawable {
        val shape = android.graphics.drawable.GradientDrawable()
        shape.shape = android.graphics.drawable.GradientDrawable.OVAL
        shape.setColor(color)
        if (isSelected) {
            shape.setStroke(6, android.graphics.Color.BLACK)
        }
        return shape
    }

    private fun updateDateTimeFields(dialogBinding: DialogNoteEditorBinding) {
        if (selectedDate != null) {
            dialogBinding.etDate.setText(dateFormat.format(Date(selectedDate!!)))
            dialogBinding.etTime.setText(
                String.format(Locale.getDefault(), "%02d:%02d", selectedHour, selectedMinute)
            )
        }
    }

    private fun showReminderFields(dialogBinding: DialogNoteEditorBinding, show: Boolean) {
        val visibility = if (show) View.VISIBLE else View.GONE
        dialogBinding.reminderFields.visibility = visibility
        dialogBinding.labelRecurrence.visibility = visibility
        dialogBinding.radioRecurrence.visibility = visibility
    }

    private fun setRecurrenceRadio(dialogBinding: DialogNoteEditorBinding, recurrence: RecurrenceType) {
        val id = when (recurrence) {
            RecurrenceType.NONE -> R.id.radioNone
            RecurrenceType.DAILY -> R.id.radioDaily
            RecurrenceType.WEEKLY -> R.id.radioWeekly
            RecurrenceType.MONTHLY -> R.id.radioMonthly
            RecurrenceType.YEARLY -> R.id.radioYearly
        }
        dialogBinding.radioRecurrence.check(id)
    }

    private fun getSelectedRecurrence(dialogBinding: DialogNoteEditorBinding): RecurrenceType {
        val checkedId = dialogBinding.radioRecurrence.checkedRadioButtonId
        return when (checkedId) {
            R.id.radioDaily -> RecurrenceType.DAILY
            R.id.radioWeekly -> RecurrenceType.WEEKLY
            R.id.radioMonthly -> RecurrenceType.MONTHLY
            R.id.radioYearly -> RecurrenceType.YEARLY
            else -> RecurrenceType.NONE
        }
    }

    private fun getSelectedCategoryId(dialogBinding: DialogNoteEditorBinding): String {
        val checkedChipId = dialogBinding.chipGroupCategory.checkedChipId
        if (checkedChipId == View.NO_ID) return ""
        val chip = dialogBinding.chipGroupCategory.findViewById<View>(checkedChipId) ?: return ""
        return chip.tag as? String ?: ""
    }

    private fun updatePhotoPreview(dialogBinding: DialogNoteEditorBinding, path: String) {
        val bitmap = BitmapFactory.decodeFile(path)
        if (bitmap != null) {
            dialogBinding.imgPhotoPreview.setImageBitmap(bitmap)
            dialogBinding.imgPhotoPreview.visibility = View.VISIBLE
            dialogBinding.btnRemovePhoto.visibility = View.VISIBLE
            dialogBinding.textPhotoStatus.text = getString(R.string.photo_add)
        } else {
            dialogBinding.imgPhotoPreview.visibility = View.GONE
            dialogBinding.btnRemovePhoto.visibility = View.GONE
            dialogBinding.textPhotoStatus.text = getString(R.string.photo_error)
        }
    }

    private fun resetEditorState() {
        selectedDate = null
        selectedHour = 0
        selectedMinute = 0
        currentPhotoPath = ""
    }

    private fun utcMillisToLocalDate(utcMillis: Long): Long {
        val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply { timeInMillis = utcMillis }
        val local = Calendar.getInstance().apply {
            set(utc.get(Calendar.YEAR), utc.get(Calendar.MONTH), utc.get(Calendar.DAY_OF_MONTH), 0, 0, 0)
            set(Calendar.MILLISECOND, 0)
        }
        return local.timeInMillis
    }

    private fun localDateToUtcMillis(localMillis: Long): Long {
        val local = Calendar.getInstance().apply { timeInMillis = localMillis }
        val utc = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
            set(local.get(Calendar.YEAR), local.get(Calendar.MONTH), local.get(Calendar.DAY_OF_MONTH), 0, 0, 0)
            set(Calendar.MILLISECOND, 0)
        }
        return utc.timeInMillis
    }

    private fun requestNotificationPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            if (ContextCompat.checkSelfPermission(
                    this, android.Manifest.permission.POST_NOTIFICATIONS
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
                    NOTIFICATION_PERMISSION_REQUEST_CODE
                )
            }
        }
    }

    private fun animateMonthTransition(toPrev: Boolean, onComplete: () -> Unit, vertical: Boolean = false) {
        val size = if (vertical) resources.displayMetrics.heightPixels else resources.displayMetrics.widthPixels
        val out = if (toPrev) size.toFloat() else -size.toFloat()
        val `in` = -out

        val anim = binding.calendarContent.animate()
            .alpha(0f)
            .setDuration(200)
            .setInterpolator(DecelerateInterpolator())
            .withEndAction {
                if (vertical) binding.calendarContent.translationY = `in`
                else binding.calendarContent.translationX = `in`
                binding.calendarContent.alpha = 0f
                onComplete()
                binding.calendarContent.animate()
                    .translationX(0f)
                    .translationY(0f)
                    .alpha(1f)
                    .setDuration(300)
                    .setInterpolator(DecelerateInterpolator())
                    .start()
            }
        if (vertical) anim.translationY(out) else anim.translationX(out)
        anim.start()
    }

    override fun onResume() {
        super.onResume()
        if (!biometricEnabled || securityManager.isUnlocked()) {
            loadNotes()
            applyBackgroundImage()
        }
    }

    override fun onPause() {
        super.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        isUnlocked = false
    }

    companion object {
        private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 100
        private const val RECORD_AUDIO_REQUEST_CODE = 101
    }
}
