package com.tomhempel.rbarometer.utils

import android.content.Context
import android.os.Build
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.widget.TextViewCompat
import com.tomhempel.rbarometer.R
import kotlin.math.roundToInt

object ViewUtils {

    const val QUESTION_TITLE_SIZE = 0.03f
    const val QUESTION_SUBTITLE_SIZE = 0.025f
    const val QUESTION_VALUE_SIZE = 0.028f
    const val CHIP_TEXT_SIZE = 0.022f

    /** Text scale for all-in-one embedded layout (replaces post-bind [reduceTextSizes] pass). */
    const val EMBEDDED_TEXT_SCALE = 0.82f

    private const val SIMPLE_SPINNER_ITEM_THRESHOLD = 20
    private const val SPINNER_MIN_HEIGHT_DP = 48
    private const val SPINNER_ARROW_SIZE_DP = 20
    private const val SPINNER_ARROW_SIZE_COMPACT_DP = 14
    private const val SPINNER_ARROW_EDGE_PAD_DP = 2
    private const val LANGUAGE_PICKER_MIN_HEIGHT_DP = 44
    private const val LANGUAGE_PICKER_MIN_WIDTH_DP = 112
    private const val LANGUAGE_PICKER_H_PAD_DP = 10

    private var textSizeScale = 1f
    private var cachedSpinnerStyleKey: String? = null
    private var cachedSpinnerStyle: SpinnerStyle? = null
    private var cachedCompactSpinnerStyleKey: String? = null
    private var cachedCompactSpinnerStyle: SpinnerStyle? = null
    private var cachedLanguagePickerStyleKey: String? = null
    private var cachedLanguagePickerStyle: SpinnerStyle? = null

    private data class SpinnerStyle(
        val textSp: Float,
        val vPadPx: Int,
        val arrowSizePx: Int,
        val rowHeight: Int,
        val textColor: Int,
        val compact: Boolean,
        val languagePicker: Boolean = false,
    )

    fun setTextSizeScale(scale: Float) {
        textSizeScale = scale
        cachedSpinnerStyleKey = null
        cachedSpinnerStyle = null
        cachedCompactSpinnerStyleKey = null
        cachedCompactSpinnerStyle = null
        cachedLanguagePickerStyleKey = null
        cachedLanguagePickerStyle = null
    }

    fun scaledScreenHeightPercent(basePercent: Float): Float = basePercent * textSizeScale

    /** Pixels per 1sp (replaces deprecated [DisplayMetrics.scaledDensity]). */
    fun spScale(displayMetrics: DisplayMetrics): Float =
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1f, displayMetrics)

    /** Suppresses AutofillManager churn during bulk programmatic text updates. */
    fun suppressAutofillForSubtree(root: View) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            root.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS
        }
    }

    fun styleQuestionTitle(view: TextView) =
        setTextSizePercentOfScreenHeight(view, QUESTION_TITLE_SIZE)

    fun styleQuestionSubtitle(view: TextView) =
        setTextSizePercentOfScreenHeight(view, QUESTION_SUBTITLE_SIZE)

    fun styleQuestionValue(view: TextView) =
        setTextSizePercentOfScreenHeight(view, QUESTION_VALUE_SIZE)

    fun styleChipText(view: TextView) =
        setTextSizePercentOfScreenHeight(view, CHIP_TEXT_SIZE)

    /** Spinner field chrome (border + trailing arrow pinned to the field edge). */
    fun styleFormSpinner(spinner: Spinner, compact: Boolean = false) {
        spinner.setBackgroundResource(
            if (compact) R.drawable.bg_field_dropdown_compact else R.drawable.bg_field_dropdown,
        )
        spinner.setPopupBackgroundResource(R.drawable.bg_spinner_popup)
        spinner.minimumHeight = dp(spinner.context, SPINNER_MIN_HEIGHT_DP)
        spinner.setPaddingRelative(0, 0, 0, 0)
    }

    /** Hub / header language picker (matches upload overview). */
    fun styleLanguagePickerSpinner(spinner: Spinner) {
        spinner.setBackgroundResource(R.drawable.bg_control_chip_dropdown)
        spinner.setPopupBackgroundResource(R.drawable.bg_spinner_popup)
        spinner.minimumHeight = dp(spinner.context, LANGUAGE_PICKER_MIN_HEIGHT_DP)
        spinner.minimumWidth = dp(spinner.context, LANGUAGE_PICKER_MIN_WIDTH_DP)
        spinner.setPaddingRelative(0, 0, 0, 0)
    }

    /** Free-text and code entry fields. */
    fun styleFormEditText(field: EditText) {
        field.setBackgroundResource(R.drawable.bg_edit_text)
        field.setTextColor(ContextCompat.getColor(field.context, R.color.brand_text_dark))
        field.setHintTextColor(ContextCompat.getColor(field.context, R.color.brand_text_muted))
    }

    /**
     * Sets the text size of a TextView to a percentage of the screen height (in sp).
     * Disables auto-sizing to prevent conflicts.
     */
    fun setTextSizePercentOfScreenHeight(view: TextView, percentOfHeight: Float) {
        val dm = view.context.resources.displayMetrics
        val shortSide = minOf(dm.heightPixels, dm.widthPixels)
        val sp = (shortSide * percentOfHeight * textSizeScale) / spScale(dm)
        if (TextViewCompat.getAutoSizeTextType(view) != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE) {
            TextViewCompat.setAutoSizeTextTypeWithDefaults(view, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE)
        }
        view.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp)
    }

    /**
     * Sets up a Spinner with a responsive, styled adapter.
     * Font size and row height are derived from screen height to prevent clipping.
     *
     * @param compact Use a smaller trailing arrow for narrow fields (e.g. date day/year).
     */
    fun <T> setupResponsiveSpinner(
        context: Context,
        spinner: Spinner,
        items: List<T>,
        selectedItem: T?,
        compact: Boolean = false,
    ) {
        styleFormSpinner(spinner, compact)
        val style = spinnerStyle(context, compact)
        if (items.size > SIMPLE_SPINNER_ITEM_THRESHOLD) {
            attachSimpleSpinner(spinner, items, selectedItem, style)
            return
        }
        attachStyledSpinner(spinner, items, selectedItem, style)
    }

    /** Language picker with chip chrome (upload overview / questionnaire headers). */
    fun <T> setupLanguagePickerSpinner(
        context: Context,
        spinner: Spinner,
        items: List<T>,
        selectedItem: T?,
    ) {
        styleLanguagePickerSpinner(spinner)
        val style = languagePickerSpinnerStyle(context)
        if (items.size > SIMPLE_SPINNER_ITEM_THRESHOLD) {
            attachSimpleSpinner(spinner, items, selectedItem, style)
            return
        }
        attachStyledSpinner(spinner, items, selectedItem, style)
    }

    private fun languagePickerSpinnerStyle(context: Context): SpinnerStyle {
        val dm = context.resources.displayMetrics
        val key = "${dm.widthPixels}x${dm.heightPixels}:$textSizeScale:lang"
        if (cachedLanguagePickerStyleKey == key) {
            cachedLanguagePickerStyle?.let { return it }
        }
        val style = buildSpinnerStyle(
            context = context,
            textScalePercent = 0.028f,
            minRowHeightDp = LANGUAGE_PICKER_MIN_HEIGHT_DP,
            arrowSizeDp = SPINNER_ARROW_SIZE_COMPACT_DP,
            verticalPadDp = 10,
            compact = true,
            languagePicker = true,
        )
        cachedLanguagePickerStyleKey = key
        cachedLanguagePickerStyle = style
        return style
    }

    private fun spinnerStyle(context: Context, compact: Boolean): SpinnerStyle {
        val dm = context.resources.displayMetrics
        val key = "${dm.widthPixels}x${dm.heightPixels}:$textSizeScale"
        if (!compact) {
            if (cachedSpinnerStyleKey == key) {
                cachedSpinnerStyle?.let { return it }
            }
        } else if (cachedCompactSpinnerStyleKey == key) {
            cachedCompactSpinnerStyle?.let { return it }
        }

        val style = buildSpinnerStyle(
            context = context,
            textScalePercent = if (compact) 0.026f else 0.0275f,
            minRowHeightDp = SPINNER_MIN_HEIGHT_DP,
            arrowSizeDp = if (compact) SPINNER_ARROW_SIZE_COMPACT_DP else SPINNER_ARROW_SIZE_DP,
            verticalPadDp = if (compact) 8 else 10,
            compact = compact,
        )
        if (compact) {
            cachedCompactSpinnerStyleKey = key
            cachedCompactSpinnerStyle = style
        } else {
            cachedSpinnerStyleKey = key
            cachedSpinnerStyle = style
        }
        return style
    }

    private fun buildSpinnerStyle(
        context: Context,
        textScalePercent: Float,
        minRowHeightDp: Int,
        arrowSizeDp: Int,
        verticalPadDp: Int,
        compact: Boolean,
        languagePicker: Boolean = false,
    ): SpinnerStyle {
        val dm = context.resources.displayMetrics
        val scale = spScale(dm)
        val shortSide = minOf(dm.heightPixels, dm.widthPixels)
        val textSp = (shortSide * scaledScreenHeightPercent(textScalePercent)) / scale
        val textPx = (textSp * scale).roundToInt()
        val vPadPx = dp(context, verticalPadDp)
        val rowHeight = maxOf(
            dp(context, minRowHeightDp),
            textPx + 2 * vPadPx + dp(context, 4),
        )
        return SpinnerStyle(
            textSp = textSp,
            vPadPx = vPadPx,
            arrowSizePx = dp(context, arrowSizeDp),
            rowHeight = rowHeight,
            textColor = ContextCompat.getColor(context, R.color.brand_text_dark),
            compact = compact,
            languagePicker = languagePicker,
        )
    }

    private fun TextView.styleSpinnerRow(style: SpinnerStyle, dropdown: Boolean) {
        setTextColor(style.textColor)
        setTextSize(TypedValue.COMPLEX_UNIT_SP, style.textSp)
        if (style.languagePicker && !dropdown) {
            setTypeface(typeface, android.graphics.Typeface.BOLD)
        } else {
            setTypeface(typeface, android.graphics.Typeface.NORMAL)
        }
        includeFontPadding = false
        setLineSpacing(0f, 1f)
        gravity = Gravity.START or Gravity.CENTER_VERTICAL
        isSingleLine = true
        minHeight = style.rowHeight
        if (dropdown) {
            setCompoundDrawablesRelative(null, null, null, null)
            val hPad = dp(
                context,
                when {
                    style.languagePicker -> 16
                    style.compact -> 10
                    else -> 16
                },
            )
            setPaddingRelative(hPad, style.vPadPx, hPad, style.vPadPx)
            layoutParams = AbsListView.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                style.rowHeight,
            )
            return
        }
        applySpinnerTextInsets(style)
        layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            style.rowHeight,
        )
    }

    private fun TextView.applySpinnerTextInsets(style: SpinnerStyle) {
        setCompoundDrawablesRelative(null, null, null, null)
        val startPad = when {
            style.languagePicker -> dp(context, LANGUAGE_PICKER_H_PAD_DP)
            style.compact -> dp(context, 6)
            else -> dp(context, 8)
        }
        val endPad = style.arrowSizePx + dp(context, SPINNER_ARROW_EDGE_PAD_DP)
        setPaddingRelative(startPad, style.vPadPx, endPad, style.vPadPx)
    }

    private fun <T> attachSimpleSpinner(
        spinner: Spinner,
        items: List<T>,
        selectedItem: T?,
        style: SpinnerStyle,
    ) {
        val adapter = object : ArrayAdapter<T>(spinner.context, R.layout.simple_spinner_item, items) {
            override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
                val v = super.getView(position, convertView, parent) as TextView
                v.styleSpinnerRow(style, dropdown = false)
                return v
            }

            override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
                val v = super.getDropDownView(position, convertView, parent) as TextView
                v.styleSpinnerRow(style, dropdown = true)
                return v
            }
        }
        adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
        spinner.adapter = adapter
        spinner.minimumHeight = style.rowHeight
        applySpinnerSelection(spinner, items, selectedItem)
    }

    private fun <T> attachStyledSpinner(
        spinner: Spinner,
        items: List<T>,
        selectedItem: T?,
        style: SpinnerStyle,
    ) {
        val adapter = object : ArrayAdapter<T>(spinner.context, R.layout.simple_spinner_item, items) {
            override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
                val v = super.getView(position, convertView, parent) as TextView
                v.styleSpinnerRow(style, dropdown = false)
                return v
            }

            override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
                val v = super.getDropDownView(position, convertView, parent) as TextView
                v.styleSpinnerRow(style, dropdown = true)
                return v
            }
        }

        adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item)
        spinner.adapter = adapter
        spinner.minimumHeight = style.rowHeight
        applySpinnerSelection(spinner, items, selectedItem)
    }

    private fun <T> applySpinnerSelection(spinner: Spinner, items: List<T>, selectedItem: T?) {
        selectedItem ?: return
        val index = items.indexOf(selectedItem)
        if (index >= 0 && (spinner.adapter?.count ?: 0) > index) {
            spinner.setSelection(index, false)
        }
    }

    /**
     * Locks an EditText field visually and functionally (e.g. for coach code fields).
     */
    fun lockEditField(field: EditText, dpPadding: Int = 8) {
        field.isFocusable = false
        field.isFocusableInTouchMode = false
        field.isCursorVisible = false
        field.keyListener = null
        field.isLongClickable = false
        field.isClickable = false
        field.setBackgroundResource(R.drawable.bg_field_locked)
        field.setTextColor(ContextCompat.getColor(field.context, R.color.brand_text_dark))
        field.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_lock_24, 0)
        field.compoundDrawablePadding = dp(field.context, dpPadding)
        field.alpha = 0.95f
    }

    /**
     * Converts dp to pixels using the given context.
     */
    fun dp(context: Context, value: Int): Int =
        (value * context.resources.displayMetrics.density).roundToInt()
}
