package com.ponzischeme89.memby.ui

import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.HomeRow
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Test

/**
 * The keyed lazy lists up and down this app are keyed by ids that came off a wire, and a
 * repeated key is a crash that takes the whole screen with it. These pin the rule that
 * stands between a duplicate and that crash — see [distinctForKeys].
 */
class ListKeysTest {

    private fun item(id: String, name: String = id) = BaseItem(id = id, name = name)

    private fun row(id: String, vararg items: BaseItem) =
        HomeRow(id = id, title = id, kind = "latest", items = items.toList())

    @Test
    fun `a list already distinct is handed back untouched`() {
        val items = listOf(item("a"), item("b"))
        assertSame(items, items.distinctForKeys(BaseItem::id))
    }

    // The first wins, deliberately: on the launcher that is the copy already drawn, and
    // pulling a card out from under somebody mid-scroll is the thing being avoided.
    @Test
    fun `a duplicate key keeps the first occurrence`() {
        val first = item("a", name = "Original")
        val items = listOf(first, item("b"), item("a", name = "Repeat"))
        val distinct = items.distinctForKeys(BaseItem::id)
        assertEquals(listOf("a", "b"), distinct.map(BaseItem::id))
        assertEquals("Original", distinct.first().name)
    }

    @Test
    fun `an empty list is safe`() {
        assertEquals(emptyList<BaseItem>(), emptyList<BaseItem>().distinctForKeys(BaseItem::id))
    }

    @Test
    fun `items without identities are removed before lazy rendering`() {
        val sanitised = listOf(row("row", BaseItem(name = "Incomplete"), item("valid")))
            .sanitisedRows()

        assertEquals(listOf("valid"), sanitised.single().items.map(BaseItem::id))
    }

    // Two rows under one id crash the launcher's own LazyColumn — which the recommendation
    // refresh could produce by appending a freshly built row beside the one it was meant to
    // replace.
    @Test
    fun `rows are made unique by row id`() {
        val rows = listOf(
            row("continue", item("a")),
            row("latest", item("b")),
            row("continue", item("c")),
        )
        assertEquals(listOf("continue", "latest"), rows.sanitisedRows().map(HomeRow::id))
    }

    // And two cards under one id crash whichever row holds them.
    @Test
    fun `cards within a row are made unique by item id`() {
        val sanitised = row("continue", item("a"), item("b"), item("a")).let {
            listOf(it).sanitisedRows()
        }
        assertEquals(listOf("a", "b"), sanitised.single().items.map(BaseItem::id))
    }

    // Sanitising runs on every home response, so the ordinary case must not rebuild the
    // whole payload: an untouched row is the same instance it arrived as.
    @Test
    fun `a clean payload is not copied`() {
        val clean = row("continue", item("a"), item("b"))
        assertSame(clean, listOf(clean).sanitisedRows().single())
    }
}
