/*
 * Copyright (c) 2025 Element Creations Ltd.
 * Copyright 2023-2025 New Vector Ltd.
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
 * Please see LICENSE files in the repository root for full details.
 */

package io.element.android.libraries.matrix.api

import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler
import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.spaces.SpaceService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.util.Optional

interface MatrixClient {
    val sessionId: SessionId
    val deviceId: DeviceId
    val userProfile: StateFlow<MatrixUser>
    val roomListService: RoomListService
    val spaceService: SpaceService
    val syncService: SyncService
    val sessionVerificationService: SessionVerificationService
    val pushersService: PushersService
    val notificationService: NotificationService
    val notificationSettingsService: NotificationSettingsService
    val encryptionService: EncryptionService
    val roomDirectoryService: RoomDirectoryService
    val mediaPreviewService: MediaPreviewService
    val matrixMediaLoader: MatrixMediaLoader
    val sessionCoroutineScope: CoroutineScope
    val ignoredUsersFlow: StateFlow<ImmutableList<UserId>>
    val roomMembershipObserver: RoomMembershipObserver
    suspend fun getJoinedRoom(roomId: RoomId): JoinedRoom?
    suspend fun getRoom(roomId: RoomId): BaseRoom?
    suspend fun findDM(userId: UserId): Result<RoomId?>
    suspend fun getJoinedRoomIds(): Result<Set<RoomId>>
    suspend fun ignoreUser(userId: UserId): Result<Unit>
    suspend fun unignoreUser(userId: UserId): Result<Unit>
    suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId>
    suspend fun createDM(userId: UserId): Result<RoomId>
    suspend fun getProfile(userId: UserId): Result<MatrixUser>
    suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults>
    suspend fun setDisplayName(displayName: String): Result<Unit>
    suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
    suspend fun removeAvatar(): Result<Unit>
    suspend fun joinRoom(roomId: RoomId): Result<RoomInfo?>
    suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomInfo?>
    suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomInfo?>
    suspend fun getCacheSize(): Long
    suspend fun getDatabaseSizes(): Result<SdkStoreSizes>

    /**
     * Will close the client and delete the cache data.
     */
    suspend fun clearCache()

    /**
     * Logout the user.
     *
     * @param userInitiated if false, the logout came from the HS, no request will be made and the session entry will be kept in the store.
     * @param ignoreSdkError if true, the SDK will ignore any error and delete the session data anyway.
     */
    suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean)

    /**
     * Retrieve the user profile, will also eventually emit a new value to [userProfile].
     */
    suspend fun getUserProfile(): Result<MatrixUser>
    suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?>
    suspend fun uploadMedia(mimeType: String, data: ByteArray): Result<String>

    /**
     * Get a room info flow for a given room ID.
     * The flow will emit a new value whenever the room info is updated.
     * The flow will emit Optional.empty item if the room is not found.
     */
    fun getRoomInfoFlow(roomId: RoomId): Flow<Optional<RoomInfo>>

    fun isMe(userId: UserId?) = userId == sessionId

    suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit>
    suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>>

    /**
     * Resolves the given room alias to a roomID (and a list of servers), if possible.
     * @param roomAlias the room alias to resolve
     * @return the resolved room alias if any, an empty result if not found,or an error if the resolution failed.
     *
     */
    suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<Optional<ResolvedRoomAlias>>

    /**
     * Enables or disables the sending queue, according to the given parameter.
     *
     * The sending queue automatically disables itself whenever sending an
     * event with it failed (e.g. sending an event via the Timeline),
     * so it's required to manually re-enable it as soon as
     * connectivity is back on the device.
     */
    suspend fun setAllSendQueuesEnabled(enabled: Boolean)

    /**
     * Returns a flow of room IDs that have send queue being disabled.
     * This flow will emit a new value whenever the send queue is disabled for a room.
     */
    fun sendQueueDisabledFlow(): Flow<RoomId>

    /**
     * Return the server name part of the current user ID, using the SDK, and if a failure occurs,
     * compute it manually.
     */
    fun userIdServerName(): String

    /**
     * Execute generic GET requests through the SDKs internal HTTP client.
     */
    suspend fun getUrl(url: String): Result<ByteArray>

    /**
     * Get a room preview for a given room ID or alias. This is especially useful for rooms that the user is not a member of, or hasn't joined yet.
     */
    suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<NotJoinedRoom>

    /**
     * Returns the currently used sliding sync version.
     */
    suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion>

    fun canDeactivateAccount(): Boolean
    suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit>

    /**
     * Check if the user can report a room.
     */
    suspend fun canReportRoom(): Boolean

    /**
     * Return true if Livekit Rtc is supported, i.e. if Element Call is available.
     */
    suspend fun isLivekitRtcSupported(): Boolean

    /**
     * Returns the maximum file upload size allowed by the Matrix server.
     */
    suspend fun getMaxFileUploadSize(): Result<Long>

    /**
     * Returns the list of shared recent emoji reactions for this account.
     */
    suspend fun getRecentEmojis(): Result<List<String>>

    /**
     * Adds an emoji to the list of recent emoji reactions for this account.
     */
    suspend fun addRecentEmoji(emoji: String): Result<Unit>

    /**
     * Marks the room with the provided [roomId] as read, sending a fully read receipt for [eventId].
     *
     * This method should be used with caution as providing the [eventId] ourselves can result in incorrect read receipts.
     * Use [Timeline.markAsRead] instead when possible.
     */
    suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result<Unit>

    /**
     * Check if linking a new device using QrCode is supported by the server.
     */
    suspend fun canLinkNewDevice(): Result<Boolean>

    /**
     * Create a handler to link a new mobile device, i.e. a device capable of scanning QrCodes.
     */
    fun createLinkMobileHandler(): Result<LinkMobileHandler>

    /**
     * Create a handler to link a new desktop device, i.e. a device not capable of scanning QrCodes.
     */
    fun createLinkDesktopHandler(): Result<LinkDesktopHandler>

    /**
     * Performs a database optimization that should flush cached data and improve performance.
     */
    suspend fun performDatabaseVacuum(): Result<Unit>

    /**
     * Resets the cached client `well-known` config by the SDK.
     */
    suspend fun resetWellKnownConfig(): Result<Unit>
}

/**
 * Returns a room alias from a room alias name, or null if the name is not valid.
 * @param name the room alias name ie. the local part of the room alias.
 */
fun MatrixClient.roomAliasFromName(name: String): RoomAlias? {
    return name.takeIf { it.isNotEmpty() }
        ?.let { "#$it:${userIdServerName()}" }
        ?.takeIf { MatrixPatterns.isRoomAlias(it) }
        ?.let { tryOrNull { RoomAlias(it) } }
}
