package com.ponzischeme89.memby.ui

import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.HomeRow

/**
 * Lists made safe to hand a keyed `LazyRow`/`LazyColumn`.
 *
 * A keyed lazy list throws — hard, taking the whole launcher with it — the moment two
 * items yield the same key: *"Key … was already used"*. Every list on this app's screens
 * is keyed by an id that came off a wire, and nothing on either wire promises those ids
 * are distinct. Emby lists the same person twice on a good fraction of a real library's
 * cast; a "Because you watched" row built from two seeds can reach the same title by both;
 * a search that falls back to Emby before the import has finished can return an item the
 * library also matched; and a row assembled from two sorted lists is one merge bug away
 * from carrying an item twice. None of those are the television's fault and none of them
 * are worth a crash: a duplicate card is at worst a card drawn once instead of twice.
 *
 * So the rule is deduplicate, never disambiguate. Making the key unique by folding the
 * index into it would also stop the crash, but it would key an item by *where it is*, and
 * position is exactly what changes when a row reorders — which is what the return-focus
 * and scroll-restoration behaviour up and down this app depends on identity to survive.
 *
 * Call it where the data enters state (the view models) in preference to inside a
 * composable; where a composable is the only place it can go, keep it inside a
 * `remember(list)` so a scroll does not re-run it per frame.
 */
internal inline fun <T, K> List<T>.distinctForKeys(selector: (T) -> K): List<T> {
    if (size < 2) return this
    val seen = HashSet<K>(size)
    // The overwhelmingly common case by far is a list that is already distinct, and this
    // runs on every home response and every page appended to a shelf. Returning the
    // receiver rather than a copy of it keeps that case free — and keeps it *identical*,
    // which matters downstream where `remember` and `distinctUntilChanged` compare what
    // they are given.
    if (all { seen.add(selector(it)) }) return this
    seen.clear()
    return filter { seen.add(selector(it)) }
}

/** [distinctForKeys] for the one shape most of these lists have. */
internal fun List<BaseItem>.distinctItems(): List<BaseItem> =
    filter { it.id.isNotBlank() }.distinctForKeys(BaseItem::id)

/**
 * A home payload safe to render: rows unique by row id, and each row's cards unique by
 * item id.
 *
 * Both halves are needed and they fail in different places. Two rows sharing an id crash
 * the launcher's own `LazyColumn` — which `refreshRecommendationRows` can produce on its
 * own, since it appends a freshly built set of rows to the ones already held and only
 * three id shapes are filtered out of the old list first. Two cards sharing an id crash
 * whichever row holds them, taking the launcher down just the same.
 */
internal fun List<HomeRow>.sanitisedRows(): List<HomeRow> =
    filter { it.id.isNotBlank() }.distinctForKeys(HomeRow::id).map { row ->
        val items = row.items.distinctItems()
        if (items.size == row.items.size) row else row.copy(items = items)
    }
