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

package space.rcmd.android.sizzle.audio

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * The arpeggiator must cycle through ALL notes of a held chord (across the octave
 * range), not collapse to the last one entered — the fix for a held chord ringing
 * only its rightmost note.
 */
class ChannelArpTest {

    private fun arp() = AudioEngine.ChannelArp()

    /** The Up sequence over a chord × octaves is octave-major over the sorted notes. */
    @Test fun upCyclesWholeChordAcrossOctaves() {
        val a = arp()
        // Enter the chord out of order; it should sort ascending.
        a.startChord(67, 100, octs = 2, modeStr = "Up", stepSamp = 100.0, resetBar = false)
        a.addNote(60)
        a.addNote(64)
        assertEquals(3, a.noteCount)

        // step 0 is the first note before any advance.
        val seq = ArrayList<Int>()
        seq.add(a.currentNote())
        repeat(5) { a.advance(); seq.add(a.currentNote()) }
        // 3 notes × 2 octaves = C4 E4 G4 C5 E5 G5, then wraps to C4.
        assertEquals(listOf(60, 64, 67, 72, 76, 79), seq)
        a.advance()
        assertEquals("wraps back to the start", 60, a.currentNote())
    }

    @Test fun downStartsHandledAndStaysInRange() {
        val a = arp()
        a.startChord(60, 100, octs = 1, modeStr = "Down", stepSamp = 100.0, resetBar = false)
        a.addNote(64); a.addNote(67)
        val seen = HashSet<Int>()
        repeat(9) { seen.add(a.currentNote()); a.advance() }
        // Only the three chord notes (one octave) ever sound.
        assertEquals(setOf(60, 64, 67), seen)
    }

    @Test fun addNoteDeDuplicates() {
        val a = arp()
        a.startChord(60, 100, octs = 1, modeStr = "Up", stepSamp = 100.0, resetBar = false)
        a.addNote(60); a.addNote(60)
        assertEquals(1, a.noteCount)
    }

    @Test fun resetChordKeepsGoingWithNewChord() {
        val a = arp()
        a.startChord(60, 100, octs = 1, modeStr = "Up", stepSamp = 100.0, resetBar = false)
        a.addNote(64)
        a.advance() // move off step 0
        // A loop-back rebuilds the chord but keeps arpeggiating.
        a.resetChord(48, 100, octs = 1, modeStr = "Up", stepSamp = 100.0, resetBar = false)
        a.addNote(55)
        assertTrue(a.active)
        val seen = HashSet<Int>()
        repeat(4) { seen.add(a.currentNote()); a.advance() }
        assertEquals(setOf(48, 55), seen)
    }

    @Test fun stopClearsChord() {
        val a = arp()
        a.startChord(60, 100, octs = 1, modeStr = "Up", stepSamp = 100.0, resetBar = false)
        a.stop()
        assertEquals(0, a.noteCount)
        assertTrue(!a.active)
    }
}
