DefaultWatchlistRepository.kt

package com.louisfn.somovie.data.repository

import androidx.annotation.AnyThread
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.louisfn.somovie.core.common.annotation.DefaultDispatcher
import com.louisfn.somovie.data.database.DatabaseHelper
import com.louisfn.somovie.data.database.datasource.RemoteKeyLocalDataSource
import com.louisfn.somovie.data.database.datasource.WatchlistLocalDataSource
import com.louisfn.somovie.data.datastore.datasource.DataStoreLocalDataSource
import com.louisfn.somovie.data.datastore.model.SessionData
import com.louisfn.somovie.data.mapper.MovieMapper
import com.louisfn.somovie.data.network.body.AddToWatchlistBody
import com.louisfn.somovie.data.network.datasource.WatchlistRemoteDataSource
import com.louisfn.somovie.data.repository.paging.WatchlistRemoteMediator
import com.louisfn.somovie.data.repository.paging.mapPaging
import com.louisfn.somovie.domain.model.Movie
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.invoke
import javax.inject.Inject

interface WatchlistRepository {

    @AnyThread
    suspend fun addToWatchlist(movieId: Long)

    @AnyThread
    suspend fun removeFromWatchlist(movieId: Long)

    @AnyThread
    fun watchlistPagingChanges(
        pagingConfig: PagingConfig,
        cacheTimeout: Long = DEFAULT_CACHE_TIMEOUT,
    ): Flow<PagingData<Movie>>

    companion object {
        private const val DEFAULT_CACHE_TIMEOUT = 0L
    }
}

internal class DefaultWatchlistRepository @Inject constructor(
    private val remoteDataSource: WatchlistRemoteDataSource,
    private val localDataSource: WatchlistLocalDataSource,
    private val sessionLocalDataSource: DataStoreLocalDataSource<SessionData>,
    private val remoteKeyLocalDataSource: RemoteKeyLocalDataSource,
    private val movieMapper: MovieMapper,
    private val databaseHelper: DatabaseHelper,
    @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher,
) : WatchlistRepository {

    override fun watchlistPagingChanges(
        pagingConfig: PagingConfig,
        cacheTimeout: Long,
    ): Flow<PagingData<Movie>> =
        flow {
            val pagerFlow =
                Pager(
                    config = pagingConfig,
                    remoteMediator = createWatchlistRemoteMediator(cacheTimeout),
                    pagingSourceFactory = { localDataSource.getPagingWatchlist() },
                ).flow
            emitAll(pagerFlow)
        }
            .mapPaging(movieMapper::mapToDomain)
            .flowOn(defaultDispatcher)

    @AnyThread
    private suspend fun createWatchlistRemoteMediator(
        cacheTimeout: Long,
    ) = WatchlistRemoteMediator(
        cacheTimeout = cacheTimeout,
        remoteKeyLocalDataSource = remoteKeyLocalDataSource,
        localDataSource = localDataSource,
        remoteDataSource = remoteDataSource,
        movieMapper = movieMapper,
        databaseHelper = databaseHelper,
        accountId = getAccountId(),
    )

    override suspend fun addToWatchlist(movieId: Long) = defaultDispatcher {
        remoteDataSource.addToWatchlist(
            accountId = getAccountId(),
            addToWatchlistBody = AddToWatchlistBody(
                movieId = movieId,
                watchlist = true,
            ),
        )
        localDataSource.updateWatchlistState(movieId, true)
    }

    override suspend fun removeFromWatchlist(movieId: Long) = defaultDispatcher {
        remoteDataSource.addToWatchlist(
            accountId = getAccountId(),
            addToWatchlistBody = AddToWatchlistBody(
                movieId = movieId,
                watchlist = false,
            ),
        )
        databaseHelper.withTransaction {
            localDataSource.updateWatchlistState(movieId, false)
            localDataSource.deleteFromWatchlist(movieId)
        }
    }

    @AnyThread
    private suspend fun getAccountId(): Long =
        checkNotNull(sessionLocalDataSource.getData().account) { "Account should not be null" }
            .id
}