package com.suno.android.common_data.repos import arrow.core.Either import com.suno.android.common_core_utils.Id import com.suno.android.common_core_utils.SunoLogger import com.suno.android.common_data.mappers.clips.LocalClipData import com.suno.android.common_data.mappers.clips.SongListData import com.suno.android.common_data.mappers.clips.xAsLocalClipDataOrNull import com.suno.android.common_data.mappers.clips.xAsSongListDataOrNull import com.suno.android.common_networking.extensions.toThrowable import com.suno.android.common_networking.remote.clips.ClipsService import com.suno.android.common_networking.remote.entities.AlignedLyricsSchema import com.suno.android.common_networking.remote.entities.CanCommentSpec import com.suno.android.common_networking.remote.entities.ClipMetadataSpec import com.suno.android.common_networking.remote.entities.ClipVisibilitySpec import com.suno.android.common_networking.remote.entities.FeedTypeEnum import com.suno.android.common_networking.remote.entities.FollowingFeedRequest import com.suno.android.common_networking.remote.entities.ItemTypeEnum import com.suno.android.common_networking.remote.gen.ClipEditMetadataResponse import com.suno.android.common_networking.remote.gen.ClipVisibilityResponseEntity import com.suno.android.common_networking.remote.gen.GenService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import retrofit2.HttpException import javax.inject.Inject interface ClipsRepository { suspend fun getClipById( clipId: Id, ): Either suspend fun getLyricsForClip( clipId: Id, ): Either fun updateMetadata( clipId: Id, metadata: ClipMetadataSpec, ): Flow> fun updateAllowComments( clipId: Id, allowComments: Boolean, ): Flow fun setClipVisibility( clipId: Id, isPublic: Boolean, ): Flow> suspend fun getFollowingFeed( startIndex: Int = 0, ): Either> } class DefaultClipsRepository @Inject constructor( loggerFactory: SunoLogger.Factory, private val clipsService: ClipsService, private val genService: GenService, ) : ClipsRepository { private val logger = loggerFactory.create(this@DefaultClipsRepository) override suspend fun getClipById( clipId: Id, ): Either = clipsService.getClipById(clipId.value) .mapLeft { error -> error.toThrowable().also { logger.e(it) } } .map { it.xAsLocalClipDataOrNull() } override suspend fun getLyricsForClip( clipId: Id, ): Either = genService.getAlignedLyrics(clipId.value).mapLeft { error -> val throwable = error.toThrowable() logger.e(throwable) throwable } // Considering making this not a flow since that doesn't make as much sense for posts override fun updateMetadata( clipId: Id, metadata: ClipMetadataSpec, ): Flow> = genService.setMetadataFlow(clipId.value, metadata) .map { if (it.isSuccessful) { val body = it.body() if (body != null) { return@map Result.success(body) } } Result.failure(HttpException(it)) }.catch { logger.e(it) }.flowOn(Dispatchers.IO) override fun updateAllowComments( clipId: Id, allowComments: Boolean, ): Flow = genService.updateAllowComments( genId = clipId.value, canCommentSpec = CanCommentSpec(clipId = clipId.value, canComment = allowComments), ).catch { logger.e(it) }.flowOn(Dispatchers.IO) override fun setClipVisibility( clipId: Id, isPublic: Boolean, ): Flow> = genService.setVisibility( genId = clipId.value, clipVisibilitySpec = ClipVisibilitySpec(isPublic = isPublic), ).map { if (it.isSuccessful) { val body = it.body() if (body != null) { return@map Result.success(body) } } val exception = HttpException(it) logger.e(exception) Result.failure(exception) }.flowOn(Dispatchers.IO) override suspend fun getFollowingFeed( startIndex: Int, ): Either> = clipsService .getFollowingClipsFeed( FollowingFeedRequest( feedType = FeedTypeEnum.default, itemType = ItemTypeEnum.clip, startIndex = startIndex, ), ) .mapLeft { Unit } .map { response -> response.items?.mapNotNull { item -> item.xAsLocalClipDataOrNull()?.xAsSongListDataOrNull().also { if (it == null) { logger.w { "failed to map $item" } } } }?.toImmutableList() ?: persistentListOf() } }