package com.ponzischeme89.memby.ui.player

import android.content.Context
import android.graphics.Typeface
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import com.ponzischeme89.memby.R

/**
 * One entry in the subtitle drop-up: what it says, whether it is the choice in force, and
 * whether it can be pressed. [enabled] is false while a search is running — the row is
 * still drawn, because removing it would move everything under the viewer's thumb, but it
 * cannot take focus and so cannot start a second search.
 */
data class SubtitleMenuEntry(
    val label: String,
    val selected: Boolean,
    val enabled: Boolean = true,
    val prominent: Boolean = false,
)

/**
 * The download section's whole state.
 *
 * [available] is the gateway's answer to "can this backend fetch a subtitle at all", and
 * false hides the section rather than disabling it: a row that can never do anything is
 * worse than no row. [status] is the sentence printed above the list — it carries both the
 * waiting message and the reason there is nothing, which are the two things a viewer
 * standing in front of the set needs and cannot get anywhere else.
 */
data class SubtitleDownloadState(
    val available: Boolean = false,
    val status: String = "",
    val entries: List<SubtitleMenuEntry> = emptyList(),
    /**
     * Whether the panel has switched to being *about* downloading, which hides the track
     * list and the text-size chips. It is not decoration: the drop-up is 344dp wide by
     * about a third of a 720p screen, and holding a track list, a size row and a list of
     * search results at once squeezed the tracks down to a single visible row. One question
     * at a time, and Back is what steps out of this one.
     */
    val expanded: Boolean = false,
)

/**
 * What the track half of the panel says when the title carries no subtitles at all.
 *
 * It is a state rather than an absence because the two cases read completely differently
 * to somebody in front of a television. A list holding nothing but "Off" is indistinguishable
 * from a menu that failed to load — and the row that can do something about it sits below a
 * rule, under a heading the eye has no reason to travel to. Saying it in the track section
 * is what connects the two.
 *
 * [downloadable] is the difference between a sentence that leads somewhere and one that is
 * simply the answer: on a backend with no provider there is nothing to offer, and the honest
 * thing is to say so once rather than to leave the viewer looking for the option.
 */
data class SubtitleTracksState(
    val empty: Boolean = false,
    val downloadable: Boolean = false,
)

/**
 * Fills the drop-up's containers from plain lists.
 *
 * It lives apart from [PlayerActivity] so `SubtitleMenuScreenshotTest` can render the real
 * menu — these rows, these colours, this layout — without a player, an Emby server or a
 * decoder, the same property that makes the detail page's content composables previewable.
 * Deciding *what* the entries are stays with the activity, which is the only thing that can
 * read media3's tracks and the only thing that can talk to the repository.
 */
fun bindSubtitleMenu(
    overlay: View,
    tracks: List<SubtitleMenuEntry>,
    sizes: List<SubtitleMenuEntry>,
    downloads: SubtitleDownloadState = SubtitleDownloadState(),
    tracksState: SubtitleTracksState = SubtitleTracksState(),
    onTrack: (Int) -> Unit = {},
    onSize: (Int) -> Unit = {},
    onDownload: (Int) -> Unit = {},
) {
    val context = overlay.context
    val trackContainer = overlay.findViewById<LinearLayout>(R.id.player_subtitle_tracks)
    val sizeContainer = overlay.findViewById<LinearLayout>(R.id.player_subtitle_sizes)
    trackContainer.removeAllViews()
    sizeContainer.removeAllViews()
    overlay.findViewById<TextView>(R.id.player_subtitle_empty_notice)?.apply {
        isVisible = tracksState.empty
        setText(
            if (tracksState.downloadable) R.string.player_subtitle_none
            else R.string.player_subtitle_none_unavailable,
        )
    }
    tracks.forEachIndexed { index, entry ->
        trackContainer.addView(subtitleMenuOption(context, entry) { onTrack(index) })
    }
    sizes.forEachIndexed { index, entry ->
        sizeContainer.addView(subtitleMenuOption(context, entry, chip = true) { onSize(index) })
    }
    bindDownloadSection(overlay, downloads, onDownload)
}

private fun bindDownloadSection(
    overlay: View,
    downloads: SubtitleDownloadState,
    onDownload: (Int) -> Unit,
) {
    val section = overlay.findViewById<View>(R.id.player_subtitle_download_section) ?: return
    section.isVisible = downloads.available
    // The two halves swap: expanded means the panel is about downloading and nothing else.
    // The rule goes with the menu, or it draws a line under the panel's own top edge.
    val expanded = downloads.available && downloads.expanded
    overlay.findViewById<View>(R.id.player_subtitle_main_section).isVisible = !expanded
    overlay.findViewById<View>(R.id.player_subtitle_download_rule).isVisible = !expanded
    if (!downloads.available) return

    overlay.findViewById<TextView>(R.id.player_subtitle_download_status).apply {
        text = downloads.status
        isVisible = downloads.status.isNotBlank()
    }
    val container = overlay.findViewById<LinearLayout>(R.id.player_subtitle_downloads)
    container.removeAllViews()
    downloads.entries.forEachIndexed { index, entry ->
        container.addView(subtitleMenuOption(overlay.context, entry) { onDownload(index) })
    }
}

/**
 * One row of the drop-up. A [chip] sizes itself to its label for the text-size row.
 * Nothing carries a tick: the current choice is the plate and the green label, and a tick on
 * only some rows shifted every other label along by its width.
 */
private fun subtitleMenuOption(
    context: Context,
    entry: SubtitleMenuEntry,
    chip: Boolean = false,
    onClick: () -> Unit,
): TextView = TextView(context).apply {
    val density = context.resources.displayMetrics.density
    fun dp(value: Int) = (value * density).toInt()
    layoutParams = LinearLayout.LayoutParams(
        if (chip) ViewGroup.LayoutParams.WRAP_CONTENT else ViewGroup.LayoutParams.MATCH_PARENT,
        dp(if (chip) 40 else 46),
    ).apply {
        if (chip) marginEnd = dp(6) else bottomMargin = dp(2)
    }
    background = context.getDrawable(
        if (entry.prominent) R.drawable.next_up_primary_button
        else R.drawable.player_overlay_option_background,
    )
    if (entry.prominent) {
        setTextColor(context.getColorStateList(R.color.player_overlay_primary_option_text))
    } else {
        setTextColor(context.getColorStateList(R.color.player_overlay_option_text))
    }
    gravity = if (chip) Gravity.CENTER else Gravity.CENTER_VERTICAL
    setPadding(dp(14), 0, dp(14), 0)
    text = entry.label
    maxLines = 1
    ellipsize = TextUtils.TruncateAt.END
    textSize = if (chip) 14f else 15f
    typeface = Typeface.create("sans-serif", if (entry.selected) Typeface.BOLD else Typeface.NORMAL)
    // A disabled row keeps its place and its label and only stops being reachable, so a
    // search in flight cannot be started twice and nothing moves while it runs.
    isFocusable = entry.enabled
    isClickable = entry.enabled
    alpha = if (entry.enabled) 1f else 0.45f
    isSelected = entry.selected
    setOnClickListener { onClick() }
}
