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.Book
import com.aletheia.app.util.setBookCover

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

    private val items = mutableListOf<Book>()

    init {
        setHasStableIds(true)
    }

    fun submitList(books: List<Book>) {
        val oldItems = items.toList()
        val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize(): Int = oldItems.shelfItemCount()
            override fun getNewListSize(): Int = books.shelfItemCount()

            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                val oldIsAddItem = oldItemPosition >= oldItems.size
                val newIsAddItem = newItemPosition >= books.size
                return when {
                    oldIsAddItem && newIsAddItem -> true
                    oldIsAddItem || newIsAddItem -> false
                    else -> oldItems[oldItemPosition].id == books[newItemPosition].id
                }
            }

            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                val oldIsAddItem = oldItemPosition >= oldItems.size
                val newIsAddItem = newItemPosition >= books.size
                return when {
                    oldIsAddItem && newIsAddItem -> true
                    oldIsAddItem || newIsAddItem -> false
                    else -> oldItems[oldItemPosition].hasSameVisibleContent(books[newItemPosition])
                }
            }
        })

        items.clear()
        items.addAll(books)
        diff.dispatchUpdatesTo(this)
    }

    fun updateBook(book: Book) {
        val index = items.indexOfFirst { it.id == book.id }
        if (index < 0) return
        items[index] = book
        notifyItemChanged(index)
    }

    override fun getItemViewType(position: Int): 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 when (viewType) {
            VIEW_TYPE_BOOK -> BookViewHolder(ItemBookBinding.inflate(inflater, parent, false))
            VIEW_TYPE_ADD -> AddBookViewHolder(ItemShelfAddBinding.inflate(inflater, parent, false))
            else -> error("Unsupported bookshelf item view type: $viewType")
        }
    }

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

    override fun getItemCount(): Int = items.shelfItemCount()

    override fun getItemId(position: Int): Long =
        if (position < items.size) items[position].id else ADD_ITEM_ID

    private inner class BookViewHolder(private val binding: ItemBookBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(book: Book) {
            val context = binding.root.context
            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) }
            if (onBookLongPressed == null) {
                binding.root.setOnLongClickListener(null)
                binding.root.isLongClickable = false
            } else {
                binding.root.setOnLongClickListener {
                    onBookLongPressed.invoke(book)
                    true
                }
            }
            if (onBookActionsClicked == null) {
                binding.bookActions.visibility = View.GONE
                binding.bookActions.setOnClickListener(null)
            } else {
                binding.bookActions.visibility = View.VISIBLE
                binding.bookActions.contentDescription = context.getString(R.string.a11y_book_actions, book.title)
                binding.bookActions.setOnClickListener { anchor ->
                    onBookActionsClicked.invoke(anchor, book)
                }
            }
        }
    }

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

    private fun Book.hasSameVisibleContent(other: Book): Boolean =
        title == other.title &&
            author == other.author &&
            readingProgress == other.readingProgress &&
            currentPage == other.currentPage &&
            totalPages == other.totalPages &&
            lastRead == other.lastRead &&
            coverImage.bytesEqual(other.coverImage)

    private fun ByteArray?.bytesEqual(other: ByteArray?): Boolean = when {
        this === other -> true
        this == null || other == null -> false
        else -> contentEquals(other)
    }

    private fun List<Book>.shelfItemCount(): Int =
        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
    }
}
