MovieMapper.kt

package com.louisfn.somovie.data.mapper

import androidx.annotation.AnyThread
import com.louisfn.somovie.core.common.provider.DateTimeProvider
import com.louisfn.somovie.data.database.entity.CompanyEntity
import com.louisfn.somovie.data.database.entity.GenreEntity
import com.louisfn.somovie.data.database.entity.MovieEntity
import com.louisfn.somovie.data.database.entity.MovieProductionCountryEntity
import com.louisfn.somovie.data.database.relation.MovieWithRelations
import com.louisfn.somovie.data.network.response.MovieAccountStateResponse
import com.louisfn.somovie.data.network.response.MovieDetailsResponse
import com.louisfn.somovie.data.network.response.MovieGenreResponse
import com.louisfn.somovie.data.network.response.MovieResponse
import com.louisfn.somovie.domain.model.BackdropPath
import com.louisfn.somovie.domain.model.Movie
import com.louisfn.somovie.domain.model.PosterPath
import javax.inject.Inject

class MovieMapper @Inject constructor(
    private val genreMapper: GenreMapper,
    private val countryMapper: CountryMapper,
    private val companyMapper: CompanyMapper,
    private val dateTimeProvider: DateTimeProvider,
) {

    //region Map entity to domain

    fun mapToDomain(entities: List<MovieEntity>): List<Movie> = entities.map(::mapToDomain)

    fun mapToDomain(entity: MovieEntity): Movie =
        mapToDomain(
            movie = entity,
            countries = emptyList(),
            companies = emptyList(),
            genres = emptyList(),
        )

    fun mapToDomain(entity: MovieWithRelations): Movie =
        mapToDomain(
            movie = entity.movie,
            countries = entity.productionCountries,
            companies = entity.productionCompanies,
            genres = entity.genres,
        )

    private fun mapToDomain(
        movie: MovieEntity,
        countries: List<MovieProductionCountryEntity>,
        companies: List<CompanyEntity>,
        genres: List<GenreEntity>,
    ) = Movie(
        id = movie.id,
        title = movie.title,
        originalTitle = movie.originalTitle,
        originalLanguage = movie.originalLanguage,
        overview = movie.overview,
        releaseDate = movie.releaseDate,
        voteAverage = movie.voteAverage,
        posterPath = movie.posterPath?.let(::PosterPath),
        backdropPath = movie.backdropPath?.let(::BackdropPath),
        watchlist = movie.watchlist,
        details = movie.details?.let { details ->
            Movie.Details(
                tagline = details.tagline,
                runtime = details.runtime,
                popularity = details.popularity,
                budget = details.budget,
                revenue = details.revenue,
                voteCount = details.voteCount,
                productionCountries = countryMapper.mapToDomain(countries),
                productionCompanies = companyMapper.mapToDomain(companies),
                genres = genreMapper.mapToDomain(genres),
            )
        },
    )

    //endregion

    //region Map response to entity

    fun mapToEntity(responses: List<MovieResponse>, fromWatchlist: Boolean): List<MovieEntity> =
        responses.map { mapToEntity(it, fromWatchlist) }

    fun mapToEntity(
        detailsResponse: MovieDetailsResponse,
        accountStateResponse: MovieAccountStateResponse?,
    ): MovieWithRelations =
        MovieWithRelations(
            movie = MovieEntity(
                id = detailsResponse.id,
                title = detailsResponse.title,
                originalTitle = detailsResponse.originalTitle,
                originalLanguage = detailsResponse.originalLanguage,
                overview = detailsResponse.overview,
                releaseDate = detailsResponse.releaseDate,
                posterPath = detailsResponse.posterPath,
                backdropPath = detailsResponse.backdropPath,
                voteAverage = detailsResponse.voteAverage,
                watchlist = accountStateResponse?.watchlist,
                updatedAt = dateTimeProvider.now(),
                detailsUpdatedAt = dateTimeProvider.now(),
                details = MovieEntity.Details(
                    tagline = detailsResponse.tagline,
                    runtime = detailsResponse.runtime,
                    popularity = detailsResponse.popularity,
                    revenue = detailsResponse.revenue,
                    budget = detailsResponse.budget,
                    voteCount = detailsResponse.voteCount,
                ),
            ),
            genres = detailsResponse.genres.map(::mapToEntity),
            productionCompanies = companyMapper.mapToEntity(detailsResponse.productionCompanies),
            productionCountries = countryMapper.mapToEntity(
                detailsResponse.id,
                detailsResponse.productionCountries,
            ),
        )

    @AnyThread
    private fun mapToEntity(response: MovieResponse, fromWatchlist: Boolean) = MovieEntity(
        id = response.id,
        title = response.title,
        originalTitle = response.originalTitle,
        originalLanguage = response.originalLanguage,
        overview = response.overview,
        releaseDate = response.releaseDate,
        posterPath = response.posterPath,
        backdropPath = response.backdropPath,
        voteAverage = response.voteAverage,
        details = null,
        watchlist = true.takeIf { fromWatchlist },
        updatedAt = dateTimeProvider.now(),
        detailsUpdatedAt = null,
    )

    private fun mapToEntity(response: MovieGenreResponse) = GenreEntity(
        id = response.id,
        name = response.name,
    )

    //endregion

    //region Map response to domain

    @JvmName("mapToResponseToDomain")
    fun mapToDomain(responses: List<MovieResponse>): List<Movie> =
        responses.map(::mapToDomain)

    @AnyThread
    private fun mapToDomain(response: MovieResponse) = Movie(
        id = response.id,
        title = response.title,
        originalTitle = response.originalTitle,
        originalLanguage = response.originalLanguage,
        overview = response.overview,
        releaseDate = response.releaseDate,
        posterPath = response.posterPath?.let(::PosterPath),
        backdropPath = response.posterPath?.let(::BackdropPath),
        voteAverage = response.voteAverage,
        watchlist = null,
        details = null,
    )
}