package ca.ksamad.encore.mpd

/**
 * Typed builders for the command lines we send. These return the assembled, properly-quoted string
 * (no newline) that gets handed to [MpdConnection.execute]; keeping them pure makes both the
 * argument quoting and the exact wire form unit-testable without any I/O.
 *
 * This is deliberately a small, growing subset — the transport controls, option toggles, and status
 * queries a remote UI needs first. Database/queue/playlist builders get added as those features
 * land.
 */
object MpdCommands {
    // --- Status queries -----------------------------------------------------

    fun status() = MpdProtocol.command("status")

    fun currentSong() = MpdProtocol.command("currentsong")

    fun stats() = MpdProtocol.command("stats")

    /**
     * Block until one of [subsystems] changes (all of them if none given). Only legal on a
     * connection dedicated to idling — never inside a command list.
     */
    fun idle(vararg subsystems: String) =
        if (subsystems.isEmpty()) {
            MpdProtocol.command("idle")
        } else {
            MpdProtocol.command("idle", *subsystems)
        }

    fun noidle() = MpdProtocol.command("noidle")

    fun ping() = MpdProtocol.command("ping")

    // --- Authentication -----------------------------------------------------

    fun password(password: String) = MpdProtocol.command("password", password)

    // --- Transport ----------------------------------------------------------

    fun play() = MpdProtocol.command("play")

    fun playPos(pos: Int) = MpdProtocol.command("play", pos.toString())

    fun playId(songId: Int) = MpdProtocol.command("playid", songId.toString())

    fun stop() = MpdProtocol.command("stop")

    fun next() = MpdProtocol.command("next")

    fun previous() = MpdProtocol.command("previous")

    /** `pause 1`/`pause 0`; with no state MPD toggles, but we send it explicitly. */
    fun pause(paused: Boolean) = MpdProtocol.command("pause", if (paused) "1" else "0")

    /** Seek to [seconds] within the currently playing song. */
    fun seekCurrent(seconds: Double) = MpdProtocol.command("seekcur", seconds.toString())

    // --- Options / mixer ----------------------------------------------------

    fun setVolume(volume: Int) = MpdProtocol.command("setvol", volume.coerceIn(0, 100).toString())

    fun repeat(on: Boolean) = MpdProtocol.command("repeat", if (on) "1" else "0")

    fun random(on: Boolean) = MpdProtocol.command("random", if (on) "1" else "0")

    fun single(on: Boolean) = MpdProtocol.command("single", if (on) "1" else "0")

    fun consume(on: Boolean) = MpdProtocol.command("consume", if (on) "1" else "0")

    // --- Queue --------------------------------------------------------------

    fun playlistInfo() = MpdProtocol.command("playlistinfo")

    fun clear() = MpdProtocol.command("clear")

    /**
     * Add a single file/URI to the queue. With no [position] it appends to the end; otherwise the
     * value is passed as `add`'s positional argument — `"+0"` inserts right after the currently
     * playing song ("play next").
     */
    fun add(
        uri: String,
        position: String? = null,
    ) =
        if (position == null) {
            MpdProtocol.command("add", uri)
        } else {
            MpdProtocol.command("add", uri, position)
        }

    fun deleteId(songId: Int) = MpdProtocol.command("deleteid", songId.toString())

    // --- Database / library -------------------------------------------------

    /**
     * Scan the music database for new, modified, and removed files (whole library when [path] is
     * null). Respects file modification times, so unchanged files are skipped. Returns immediately
     * with `updating_db: <jobid>`; the scan runs in the background.
     */
    fun update(path: String? = null) =
        if (path == null) MpdProtocol.command("update") else MpdProtocol.command("update", path)

    /**
     * Like [update], but also re-reads files whose modification time is unchanged — the "hard"
     * refresh, for when tags changed without touching mtime. Heavier on the server.
     */
    fun rescan(path: String? = null) =
        if (path == null) MpdProtocol.command("rescan") else MpdProtocol.command("rescan", path)

    /** Every album, grouped by album artist (`Album`/`AlbumArtist` lines). */
    fun listAlbums() = MpdProtocol.command("list", "album", "group", "albumartist")

    /**
     * Append every track of an album to the queue (optionally scoped to an artist).
     *
     * When [position] is given it is passed through as `findadd`'s `position` argument, controlling
     * where the tracks land. MPD accepts an absolute index or a relative one — `"+0"` inserts right
     * after the currently playing song (i.e. "play next"). Omit it to append to the end of the
     * queue.
     */
    fun findAddAlbum(
        album: String,
        albumArtist: String?,
        position: String? = null,
    ): String {
        val args = buildList {
            add("album")
            add(album)
            if (!albumArtist.isNullOrEmpty()) {
                add("albumartist")
                add(albumArtist)
            }
            if (position != null) {
                add("position")
                add(position)
            }
        }
        return MpdProtocol.command("findadd", *args.toTypedArray())
    }

    /** Every track of an album (optionally scoped to an artist), in database order. */
    fun findAlbumTracks(
        album: String,
        albumArtist: String?,
    ) =
        if (albumArtist.isNullOrEmpty()) {
            MpdProtocol.command("find", "album", album)
        } else {
            MpdProtocol.command("find", "album", album, "albumartist", albumArtist)
        }

    /** First track of an album — used to resolve a URI for album-art lookup. */
    fun findFirstTrack(
        album: String,
        albumArtist: String?,
    ) =
        if (albumArtist.isNullOrEmpty()) {
            MpdProtocol.command("find", "album", album, "window", "0:1")
        } else {
            MpdProtocol.command("find", "album", album, "albumartist", albumArtist, "window", "0:1")
        }

    // --- Album art ----------------------------------------------------------

    /** Folder cover art for [uri]'s directory, starting at [offset] (chunked binary). */
    fun albumArt(
        uri: String,
        offset: Int,
    ) = MpdProtocol.command("albumart", uri, offset.toString())

    /** Embedded picture from the file at [uri], starting at [offset] (chunked binary). */
    fun readPicture(
        uri: String,
        offset: Int,
    ) = MpdProtocol.command("readpicture", uri, offset.toString())

    /** Raise the per-response binary chunk size so art transfers in fewer round-trips. */
    fun binaryLimit(bytes: Int) = MpdProtocol.command("binarylimit", bytes.toString())
}
