/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.widget

import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.SpannableString
import android.text.TextPaint
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.android.settingslib.widget.theme.R
import com.google.android.material.button.MaterialButton

class CollapsableTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    private var isCollapsable: Boolean = DEFAULT_COLLAPSABLE
    private var isCollapsed: Boolean = false
    private var minLines: Int = DEFAULT_MIN_LINES


    private val titleTextView: TextView
    private val collapseButton: LinearLayout
    private val collapseButtonIcon: ImageView?
    private val collapseButtonText: TextView?
    private val collapseButtonResources: CollapseButtonResources
    private var hyperlinkListener: View.OnClickListener? = null
    private var learnMoreListener: View.OnClickListener? = null
    private var learnMoreText: CharSequence? = null
    private var learnMoreSpan: LearnMoreSpan? = null
    val learnMoreTextView: LinkableTextView
    var isLearnMoreEnabled: Boolean = false

    init {
        LayoutInflater.from(context)
            .inflate(R.layout.settingslib_expressive_collapsable_textview, this)
        titleTextView = findViewById(android.R.id.title)
        collapseButton = findViewById(R.id.collapse_button)
        collapseButtonIcon = collapseButton.findViewById(android.R.id.icon1)
        collapseButtonText = collapseButton.findViewById(android.R.id.text1)
        learnMoreTextView = findViewById(R.id.settingslib_expressive_learn_more)

        collapseButtonResources = CollapseButtonResources(
            context.getDrawable(R.drawable.settingslib_expressive_icon_collapse)!!,
            context.getDrawable(R.drawable.settingslib_expressive_icon_expand)!!,
            context.getString(R.string.settingslib_expressive_text_collapse),
            context.getString(R.string.settingslib_expressive_text_expand)
        )

        collapseButton.setOnClickListener {
            isCollapsed = !isCollapsed
            updateView()
        }

        initAttributes(context, attrs, defStyleAttr)
    }

    private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
        context.obtainStyledAttributes(
            attrs, R.styleable.CollapsableTextView, defStyleAttr, 0
        ).apply {
            val gravity = getInt(gravityAttr, Gravity.START)
            when (gravity) {
                Gravity.CENTER_VERTICAL, Gravity.CENTER, Gravity.CENTER_HORIZONTAL -> {
                    centerHorizontally(titleTextView)
                    centerHorizontally(collapseButton)
                    centerHorizontally(learnMoreTextView)
                }
            }
            isCollapsable = getBoolean(isCollapsableAttr, DEFAULT_COLLAPSABLE)
            minLines = getInt(minLinesAttr, DEFAULT_MIN_LINES)
            recycle()
        }
    }

    private fun centerHorizontally(view: View) {
        when (view) {
            is MaterialButton -> {
                (view.layoutParams as LayoutParams).apply {
                    startToStart = LayoutParams.PARENT_ID
                    endToEnd = LayoutParams.PARENT_ID
                }
            }
            is TextView -> {
                view.gravity = Gravity.CENTER
                // Do not remove! This line was discovered to be necessary for GMSCore.
                view.textAlignment = View.TEXT_ALIGNMENT_CENTER
            }
            else -> {
                (view.layoutParams as LayoutParams).apply {
                    startToStart = LayoutParams.PARENT_ID
                    endToEnd = LayoutParams.PARENT_ID
                }
            }
        }
    }

    override fun onMeasure(p0: Int, p1: Int) {
        super.onMeasure(p0, p1)
        updateView()
    }

    /**
     * Sets the text content of the CollapsableTextView.
     * @param text The text to display.
     */
    fun setText(text: String) {
        titleTextView.text = text
    }

    /**
     * Sets whether the text view is collapsable.
     * @param collapsable True if the text view should be collapsable, false otherwise.
     */
    fun setCollapsable(collapsable: Boolean) {
        isCollapsable = collapsable
        // Make is collapsed when it's collapsable
        if (isCollapsable) isCollapsed = true
        updateView()
    }

    /**
     * Sets the minimum number of lines to display when collapsed.
     * @param lines The minimum number of lines.
     */
    fun setMinLines(lines: Int) {
        minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
        updateView()
    }

    /**
     * Sets the action when clicking on the learn more view.
     * @param listener The click listener for learn more.
     */
    fun setLearnMoreAction(listener: View.OnClickListener?) {
        if (learnMoreListener != listener) {
            learnMoreListener = listener
            formatLearnMoreText()
        }
    }

    /**
     * Sets the text of learn more view.
     * @param text The text of learn more.
     */
    fun setLearnMoreText(text: CharSequence?) {
        if (!TextUtils.equals(learnMoreText, text)) {
            learnMoreText = text
            formatLearnMoreText()
        }
    }

    fun setHyperlinkListener(listener: View.OnClickListener?) {
        if (hyperlinkListener != listener) {
            hyperlinkListener = listener
            linkifyTitle()
        }
    }

    private fun linkifyTitle() {
        var text = titleTextView.text.toString()
        val beginIndex = text.indexOf(LINK_BEGIN_MARKER)
        text = text.replace(LINK_BEGIN_MARKER, "")
        val endIndex = text.indexOf(LINK_END_MARKER)
        text = text.replace(LINK_END_MARKER, "")
        titleTextView.text = text
        if (beginIndex == -1 || endIndex == -1 || beginIndex >= endIndex) {
            return
        }

        titleTextView.setText(text, TextView.BufferType.SPANNABLE)
        titleTextView.movementMethod = LinkMovementMethod.getInstance()
        val spannableContent = titleTextView.getText() as Spannable
        val spannableLink = object : ClickableSpan() {
            override fun onClick(widget: View) {
                hyperlinkListener?.onClick(widget)
            }

            override fun updateDrawState(ds: TextPaint) {
                super.updateDrawState(ds)
                ds.isUnderlineText = true
            }
        }
        spannableContent.setSpan(
            spannableLink,
            beginIndex,
            endIndex,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
    }

    private fun formatLearnMoreText() {
        if (learnMoreListener == null || TextUtils.isEmpty(learnMoreText)) {
            learnMoreTextView.visibility = GONE
            isLearnMoreEnabled = false
            return
        }
        val spannableLearnMoreText = SpannableString(learnMoreText)
        if (learnMoreSpan != null) {
            spannableLearnMoreText.removeSpan(learnMoreSpan)
        }
        learnMoreSpan = LearnMoreSpan(clickListener = learnMoreListener!!)
        spannableLearnMoreText.setSpan(learnMoreSpan, 0, learnMoreText!!.length, 0)
        learnMoreTextView.text = spannableLearnMoreText
        learnMoreTextView.visibility = VISIBLE
        isLearnMoreEnabled = true
    }

    private fun updateView() {
        when {
            isCollapsed -> {
                collapseButtonIcon?.setImageDrawable(collapseButtonResources.expandIcon)
                collapseButtonText?.text = collapseButtonResources.expandText
                titleTextView.maxLines = minLines
                titleTextView.ellipsize = null
                titleTextView.scrollBarSize = 0
            }

            else -> {
                collapseButtonIcon?.setImageDrawable(collapseButtonResources.collapseIcon)
                collapseButtonText?.text = collapseButtonResources.collapseText
                titleTextView.maxLines = DEFAULT_MAX_LINES
                titleTextView.ellipsize = TextUtils.TruncateAt.END
            }
        }
        collapseButton.visibility =
            if (isCollapsable && titleTextView.lineCount > minLines) VISIBLE else GONE
        learnMoreTextView.visibility = if (isLearnMoreEnabled && !isCollapsed) VISIBLE else GONE
    }

    private data class CollapseButtonResources(
        val collapseIcon: Drawable,
        val expandIcon: Drawable,
        val collapseText: String,
        val expandText: String
    )

    companion object {
        private const val DEFAULT_MAX_LINES = 50
        private const val DEFAULT_MIN_LINES = 2
        private const val DEFAULT_COLLAPSABLE = true

        private const val LINK_BEGIN_MARKER = "LINK_BEGIN"
        private const val LINK_END_MARKER = "LINK_END"

        private val gravityAttr = R.styleable.CollapsableTextView_android_gravity
        private val minLinesAttr = R.styleable.CollapsableTextView_android_minLines
        private val isCollapsableAttr = R.styleable.CollapsableTextView_isCollapsable
    }
}

internal class LearnMoreSpan(
    url: String = "",
    val clickListener: View.OnClickListener) : URLSpan(url) {
    override fun onClick(widget: View) {
        clickListener.onClick(widget)
    }
}
