package ca.ksamad.encore.mpd

import java.io.IOException

/** Base type for every failure the MPD layer can raise. */
sealed class MpdException(
    message: String,
    cause: Throwable? = null,
) : IOException(message, cause)

/**
 * A transport-level problem: the socket closed, the greeting was malformed, an EOF arrived
 * mid-response, etc. These are the failures that mean "the connection can no longer be trusted", as
 * opposed to a command the server simply rejected.
 */
class MpdConnectionException(
    message: String,
    cause: Throwable? = null,
) : MpdException(message, cause)

/**
 * The server understood us but refused the command, i.e. it replied with an `ACK` line. The wire
 * format is:
 * ```
 * ACK [error@command_listNum] {current_command} message_text
 * ```
 *
 * where `error` is one of MPD's numeric `ACK_ERROR_*` codes (see [Code]), `command_listNum` is the
 * 0-based offset of the failing command inside a command list (0 for a bare command), and
 * `current_command` names it.
 */
class MpdAckException(
    val code: Int,
    val commandListNum: Int,
    val command: String,
    val serverMessage: String,
) : MpdException("ACK [$code@$commandListNum] {$command} $serverMessage") {
    /** MPD's `ACK_ERROR_*` constants, for callers that want to branch on them. */
    object Code {
        const val NOT_LIST = 1
        const val ARG = 2
        const val PASSWORD = 3
        const val PERMISSION = 4
        const val UNKNOWN = 5
        const val NO_EXIST = 50
        const val PLAYLIST_MAX = 51
        const val SYSTEM = 52
        const val PLAYLIST_LOAD = 53
        const val UPDATE_ALREADY = 54
        const val PLAYER_SYNC = 55
        const val EXIST = 56
    }

    companion object {
        // ACK [2@1] {play} Bad song index
        // NB: `]` and `}` are escaped. The JVM regex engine tolerates them bare,
        // but Android's ICU engine rejects an unescaped `]`/`}`, which would make
        // this Regex fail to compile at class-init time (only on-device).
        private val PATTERN = Regex("""ACK \[(\d+)@(\d+)\] \{([^}]*)\} ?(.*)""")

        /**
         * Parse a raw `ACK …` response line. Returns `null` if [line] is not a well-formed ACK, so
         * the caller can decide how to treat garbage.
         */
        fun parse(line: String): MpdAckException? {
            val m = PATTERN.matchEntire(line) ?: return null
            val (code, listNum, command, message) = m.destructured
            return MpdAckException(
                code = code.toInt(),
                commandListNum = listNum.toInt(),
                command = command,
                serverMessage = message,
            )
        }
    }
}
