// SPDX-FileCopyrightText: 2026 Reactorcoremeltdown
// SPDX-License-Identifier: GPL-3.0-or-later

package space.rcmd.android.sizzle.model

/**
 * One cell in the tracker grid: a single note event on a single line of a single
 * track. All three fields map directly to the three on-screen columns:
 *  - [note]     : MIDI note number, or [EMPTY], or [OFF] (note-off).
 *  - [velocity] : 0..127 (MIDI range), shown to the user as two hex digits (00..7F).
 *  - [channel]  : MIDI channel 1..16, shown as a decimal.
 *
 * We use plain mutable fields (not a `data class` copy) because the tracker grid
 * is edited cell-by-cell in tight gestures and we want to avoid allocating a new
 * object on every drag tick.
 */
class Cell(
    var note: Int = EMPTY,
    var velocity: Int = MAX_VELOCITY, // default to full velocity so new notes are audible
    var channel: Int = 1,
) {
    val isEmpty: Boolean get() = note == EMPTY
    val isNoteOff: Boolean get() = note == OFF
    val isPlayable: Boolean get() = note in Pitch.LOWEST..Pitch.HIGHEST

    fun clear() {
        note = EMPTY
        velocity = MAX_VELOCITY
        // channel is preserved: an empty cell never sounds, and keeping it means a
        // note later entered here still routes to the track's default bus.
    }

    /** A detached copy (for the clipboard) — never aliases the source cell. */
    fun copyOf(): Cell = Cell(note, velocity, channel)

    /** Overwrite this cell's fields from [other] (paste). */
    fun setFrom(other: Cell) {
        note = other.note
        velocity = other.velocity
        channel = other.channel
    }

    companion object {
        const val EMPTY = -1
        const val OFF = -2
        /** MIDI velocity ceiling (0x7F). Values are clamped to 0..[MAX_VELOCITY]. */
        const val MAX_VELOCITY = 0x7F
        /** MIDI channel ceiling. Channels run 1..[MAX_CHANNEL]. */
        const val MAX_CHANNEL = 16
    }
}

/** The four editable columns per track. Only NOTE is pitch; the rest are values. */
enum class CellColumn { NOTE, VELOCITY, CHANNEL }

/**
 * A pattern (a "block" in arrangement terms): [TRACK_COUNT] parallel tracks, each
 * a vertical column of [length] cells. Length is one of the current time
 * signature's allowed values (see [TimeSignature.lengthOptions]).
 *
 * The grid is stored as `tracks[trackIndex][lineIndex]`.
 */
class Pattern(
    val id: Int,
    var name: String = "PTN",
    var length: Int = 16,
) {
    /** tracks[t][line]. Always [TRACK_COUNT] tracks; each list has [length] cells.
     *  New cells default to the empty channel 0 (routes to no bus until the user
     *  assigns one); routing is by channel, not track — see AudioEngine.triggerCell. */
    val tracks: Array<MutableList<Cell>> =
        Array(TRACK_COUNT) { MutableList(length) { Cell(channel = 0) } }

    fun cell(track: Int, line: Int): Cell = tracks[track][line]

    /**
     * Resize the pattern to [newLength], preserving existing cells where possible.
     * Called when the length dropdown or the time signature changes.
     */
    fun resize(newLength: Int) {
        if (newLength == length) return
        for (t in 0 until TRACK_COUNT) {
            val col = tracks[t]
            when {
                newLength > col.size -> repeat(newLength - col.size) { col.add(Cell(channel = 0)) }
                newLength < col.size -> while (col.size > newLength) col.removeAt(col.size - 1)
            }
        }
        length = newLength
    }

    companion object {
        /** Fixed by the spec: the tracker upper half always has four tracks. */
        const val TRACK_COUNT = 4
    }
}
