package com.aletheia.app.ui.books

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.aletheia.app.R
import com.aletheia.app.databinding.ItemBookBinding
import com.aletheia.app.databinding.ItemShelfAddBinding
import com.aletheia.app.model.AudioBook
import com.aletheia.app.model.Book
import com.aletheia.app.util.setBookCover

class BooksAdapter(
    private val onBookClicked: (Book) -> Unit,
    private val onAudioBookClicked: (AudioBook) -> Unit = {},
    private val onAddBookClicked: () -> Unit,
    private val onBookLongPressed: ((Book) -> Unit)? = null,
    private val onBookActionsClicked: ((View, Book) -> Unit)? = null,
    private val onAudioBookActionsClicked: ((View, AudioBook) -> Unit)? = null,
    private val includeAddItem: Boolean = true
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private val items = mutableListOf<ShelfItem>()

    init { setHasStableIds(true) }

    fun submitList(books: List<Book>) = submitContent(books, emptyList())

    fun updateBook(book: Book) {
        val currentBooks = items.mapNotNull { (it as? ShelfItem.TextBook)?.book }.map {
            if (it.id == book.id) book else it
        }
        val currentAudio = items.mapNotNull { it as? ShelfItem.Audio }.map { it.audioBook to it.coverImage }
        submitContent(currentBooks, currentAudio)
    }

    fun submitContent(books: List<Book>, audioBooks: List<Pair<AudioBook, ByteArray?>>) {
        val next = books.map(ShelfItem::TextBook) + audioBooks.map { (audioBook, cover) ->
            ShelfItem.Audio(audioBook, cover)
        }
        val old = items.toList()
        val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize() = old.itemCount()
            override fun getNewListSize() = next.itemCount()
            override fun areItemsTheSame(oldPosition: Int, newPosition: Int): Boolean {
                val oldItem = old.getOrNull(oldPosition)
                val newItem = next.getOrNull(newPosition)
                return if (oldItem == null || newItem == null) oldItem == newItem else oldItem.stableId == newItem.stableId
            }
            override fun areContentsTheSame(oldPosition: Int, newPosition: Int): Boolean {
                val oldItem = old.getOrNull(oldPosition)
                val newItem = next.getOrNull(newPosition)
                return if (oldItem == null || newItem == null) oldItem == newItem else oldItem.sameVisibleContent(newItem)
            }
        })
        items.clear()
        items.addAll(next)
        diff.dispatchUpdatesTo(this)
    }

    override fun getItemViewType(position: Int) = if (position < items.size) VIEW_TYPE_BOOK else VIEW_TYPE_ADD

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return if (viewType == VIEW_TYPE_BOOK) {
            BookViewHolder(ItemBookBinding.inflate(inflater, parent, false))
        } else {
            AddBookViewHolder(ItemShelfAddBinding.inflate(inflater, parent, false))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is BookViewHolder -> holder.bind(items[position])
            is AddBookViewHolder -> holder.bind()
        }
    }

    override fun getItemCount() = items.itemCount()
    override fun getItemId(position: Int) = items.getOrNull(position)?.stableId ?: ADD_ITEM_ID

    private inner class BookViewHolder(private val binding: ItemBookBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item: ShelfItem) {
            val context = binding.root.context
            when (item) {
                is ShelfItem.TextBook -> {
                    val book = item.book
                    binding.bookTitle.text = book.title
                    binding.bookAuthor.text = book.author
                    binding.bookProgressText.text = book.progressText
                    binding.bookProgressBar.progress = (book.readingProgress * 100).toInt()
                    binding.bookCover.setBookCover(book.coverImage, R.drawable.default_cover)
                    binding.root.contentDescription = context.getString(
                        R.string.a11y_book_card, book.title, book.author, book.progressText
                    )
                    binding.root.setOnClickListener { onBookClicked(book) }
                    binding.root.setOnLongClickListener(if (onBookLongPressed == null) null else View.OnLongClickListener {
                        onBookLongPressed.invoke(book)
                        true
                    })
                    bindActions { anchor -> onBookActionsClicked?.invoke(anchor, book) }
                }
                is ShelfItem.Audio -> {
                    val audio = item.audioBook
                    val progress = when {
                        audio.status == AudioBook.Status.GENERATING || audio.status == AudioBook.Status.QUEUED ->
                            audio.progressPercent
                        audio.durationMs > 0 -> ((audio.positionMs * 100L) / audio.durationMs).toInt().coerceIn(0, 100)
                        else -> 0
                    }
                    val status = when (audio.status) {
                        AudioBook.Status.READY -> "Аудиокнига · $progress%"
                        AudioBook.Status.GENERATING -> "Создание · ${audio.progressPercent}%"
                        AudioBook.Status.QUEUED -> "В очереди на создание"
                        AudioBook.Status.FAILED -> "Ошибка создания"
                        AudioBook.Status.CANCELLED -> "Создание отменено"
                    }
                    binding.bookTitle.text = audio.title
                    binding.bookAuthor.text = audio.author
                    binding.bookProgressText.text = status
                    binding.bookProgressBar.progress = progress
                    binding.bookCover.setBookCover(item.coverImage, R.drawable.default_cover)
                    binding.root.contentDescription = "${audio.title}. ${audio.author}. $status"
                    binding.root.setOnLongClickListener(null)
                    binding.root.setOnClickListener { onAudioBookClicked(audio) }
                    bindActions { anchor -> onAudioBookActionsClicked?.invoke(anchor, audio) }
                }
            }
        }

        private fun bindActions(action: ((View) -> Unit)?) {
            binding.bookActions.visibility = if (action == null) View.GONE else View.VISIBLE
            binding.bookActions.setOnClickListener(action)
        }
    }

    private inner class AddBookViewHolder(private val binding: ItemShelfAddBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind() { binding.root.setOnClickListener { onAddBookClicked() } }
    }

    private sealed class ShelfItem(val stableId: Long) {
        data class TextBook(val book: Book) : ShelfItem(book.id)
        data class Audio(val audioBook: AudioBook, val coverImage: ByteArray?) :
            ShelfItem(Long.MIN_VALUE + audioBook.id)

        fun sameVisibleContent(other: ShelfItem?): Boolean = when {
            this is TextBook && other is TextBook -> book.copy(coverImage = null) == other.book.copy(coverImage = null) &&
                bytesEqual(book.coverImage, other.book.coverImage)
            this is Audio && other is Audio -> audioBook == other.audioBook && bytesEqual(coverImage, other.coverImage)
            else -> false
        }

        private fun bytesEqual(first: ByteArray?, second: ByteArray?) = when {
            first === second -> true
            first == null || second == null -> false
            else -> first.contentEquals(second)
        }
    }

    private fun List<ShelfItem>.itemCount() = if (isEmpty()) 0 else size + if (includeAddItem) 1 else 0
    private companion object {
        const val VIEW_TYPE_BOOK = 1
        const val VIEW_TYPE_ADD = 2
        const val ADD_ITEM_ID = Long.MAX_VALUE
    }
}
