package mg.dot.feufaro.midi

import SharedScreenModel
import kotlinx.coroutines.*
import mg.dot.feufaro.getConfigDirectoryPath
import mg.dot.feufaro.viewmodel.MidiMarkers
import java.io.File
import java.util.prefs.Preferences
import javax.sound.midi.MetaMessage
import javax.sound.midi.MidiSystem
import javax.sound.midi.Sequence
import javax.sound.midi.Sequencer
import javax.sound.midi.ShortMessage
import javax.sound.midi.Synthesizer
import javax.sound.midi.Track
import javax.sound.sampled.AudioSystem
import javax.sound.sampled.FloatControl

actual class FMediaPlayer actual constructor(
    private val filename: String,
    private val onFinished: () -> Unit
) {
    private var sequencer: Sequencer? = try {
        MidiSystem.getSequencer(false)
    } catch (e: Exception){

        println("Erreur impossible obtenir  ${e.message}")
        null
    }

    private val prefs = Preferences.userRoot().node("mg.dot.feufaro")
    private var synthetizer = MidiSystem.getSynthesizer() as Synthesizer?

    private var pointA: Long = -1L
    private var pointB: Long = -1L
    private var isLoopingAB: Boolean = false

    private val voiceVolumes = FloatArray(4) { 127f }
    private var currentGlobalVolume: Float = 0.8f
    private var currentDynamicVelocity: Int = Dynamic.MF.velocity
    private var currentDynamicFactor: Float = Dynamic.MF.factor

    private var currentTempo: Float = 1.0f
    private var targetBpm: Float = 120f
    private val playerScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
    private var abJob: Job? = null

    private var navigationJob: Job? = null

    private var isInTempoChange: Boolean = false
    private var tempoChangeJob: Job? = null
    private data class NavigationStep(
        val marker: String,
        val gridIndex: Int,          // L'ancre absolue
        val targetGrid: Int = 0,
        val isHold: Boolean = false, // point d'orgue
        var alreadyDone: Boolean = false, // done
        var beatInDC: Int = 1,  // temps note sur le marker
        val dynamic: Dynamic?= null, // velocité
        // hairpin
        val hairPin: Char? = null,
        val hairPinEndGrid: Int = -1,
        val hairPinFromFactor: Float = 1.0f,
        val hairPinToFactor: Float = 1.0f,
        val isFin: Boolean = false,  // Farany D.C.
        var finActive: Boolean =false,
        // rit & rall
        val isTempoChange: Boolean = false,
        val tempoType: String = "",
        val endGridForTempo: Int = -1,
        val targetTempoMultiplier: Float = 0.6f

    )
    private val navigationSteps = mutableListOf<NavigationStep>()

    private var boundModel: SharedScreenModel? = null
    init {
        try {
            sequencer?.open()
            synthetizer?.open()
            val transmitter = sequencer?.transmitter
            val synthReceiver = synthetizer?.receiver
            transmitter?.receiver = synthReceiver
        } catch (e: Exception) {
            e.printStackTrace()
        }

        val file = File(filename)
        if (file.exists()) {
            val mySequence = MidiSystem.getSequence(file)
            sequencer?.sequence = mySequence
            stripTempoEvents(mySequence)
            sequencer?.tempoFactor = 1.0f
            loadVoiceVolumes()
            applyVoiceStates()
            sequencer?.addMetaEventListener { meta ->
                if(meta.type == 47){
                    val totalGrids = (sequencer!!.sequence.tickLength / sequencer!!.sequence.resolution).toInt()
                    val currentGrid = (sequencer!!.tickPosition / sequencer!!.sequence.resolution).toInt()
                    val pendingDc = getPendingDcStep()

                    val isDcNearEnd = pendingDc != null && (totalGrids - pendingDc.gridIndex) <= 2

                    if (isDcNearEnd) {
                        println("onFinished : DC pending → saut direct vers ${pendingDc.targetGrid}")
                        pendingDc.alreadyDone = true
                        seekToGrid(pendingDc.targetGrid)
                        play()
                    } else {
                        onFinished()
                    }
                }
            }
        } else {
            // Créeons une fichier vide au 1er lancement de l'application, après MidiWriterKotlin l'écrasera
            val f0file = File("${getConfigDirectoryPath()}whawyd3.mid")
            f0file.createNewFile()
        }
    }

    private fun stripTempoEvents(sequence: Sequence) {
        for (track in sequence.tracks) {
            val eventsToRemove = mutableListOf<javax.sound.midi.MidiEvent>()
            for (i in 0 until track.size()) {
                val event = track.get(i)
                val message = event.message
                if (message is MetaMessage && message.type == 0x51) { // 0x51 = Set Tempo
                    eventsToRemove.add(event)
                }
            }
            eventsToRemove.forEach { track.remove(it) }
        }
    }
    private fun applyBpm() {
        sequencer?.tempoInBPM = targetBpm
        sequencer?.tempoFactor = 1.0f
    }
    private fun forceTempo(bpm: Double) {
        sequencer?.tempoInBPM = bpm.toFloat()
        sequencer?.tempoFactor = 1.0f
    }
    private fun resetNavigationFlags() {
        navigationSteps.forEach { it.alreadyDone = false }
    }

    private var dynamicJob: Job? = null

    private fun applyDynamicSmooth(targetFactor: Float, durationMs: Long = 300L) {
        dynamicJob?.cancel()
        dynamicJob = playerScope.launch {
            val startFactor = currentDynamicFactor
            val steps = 30
            val stepMs = durationMs / steps

            for (i in 1..steps) {
                val progress = i.toFloat() / steps
                val smooth = progress * progress * (3f - 2f * progress)

                currentDynamicFactor = startFactor + (targetFactor - startFactor) * smooth
                applyVoiceStates()
                delay(stepMs)
            }
            currentDynamicFactor = targetFactor
            applyVoiceStates()
        }
    }
    private fun applyDynamic(dynamic: Dynamic) {
        val targetFactor = when (dynamic) {
            Dynamic.MF -> 1.0f
            else       -> dynamic.factor
        }
        println("applyDynamic → ${dynamic.label} | ${currentDynamicFactor} → $targetFactor")
        applyDynamicSmooth(targetFactor, durationMs = 300L)
    }
    private fun extractDynamic(marker: String): Dynamic? {
        val clean = marker.trim().removeSuffix("=").trim()
        return Dynamic.entries.firstOrNull { it.label == clean }
    }
    fun factorToDynamic(factor: Float): Dynamic = Dynamic.entries.minByOrNull { kotlin.math.abs(it.factor - factor) } ?: Dynamic.MF
    fun nextDynamic(currentFactor: Float, symbol: Char): Float {
        val current = factorToDynamic(currentFactor)
        val index   = Dynamic.entries.indexOf(current)
        return if (symbol == '<') {
            Dynamic.entries.getOrElse(index + 1) { Dynamic.FFF }.factor
        } else {
            Dynamic.entries.getOrElse(index - 1) { Dynamic.PPP }.factor
        }
    }
    private fun applyCrescendo(fromFactor: Float, toFactor: Float, durationMs: Long) {
        dynamicJob?.cancel()
        dynamicJob = playerScope.launch {
            val steps  = 40
            val stepMs = (durationMs / steps).coerceAtLeast(10L)

            for (i in 1..steps) {
                val progress = i.toFloat() / steps
                val smooth   = progress * progress * (3f - 2f * progress) // ease in-out
                currentDynamicFactor = fromFactor + (toFactor - fromFactor) * smooth
                applyVoiceStates()
                delay(stepMs)
            }
            currentDynamicFactor = toFactor
            applyVoiceStates()
            println("applyCrescendo terminé → factor=$currentDynamicFactor")
        }
    }
    private fun applyTempoChange(
        startBpm: Float,
        endBpm: Float,
        durationMs: Long,
        tempoType: String
    ) {
        tempoChangeJob?.cancel()
        isInTempoChange = true
        tempoChangeJob = playerScope.launch {
            val steps = 50
            val stepMs = (durationMs / steps).coerceAtLeast(20L)
            val startTime = System.currentTimeMillis()

            for (i in 1..steps) {
                val elapsed = System.currentTimeMillis() - startTime
                val progress = (elapsed.toFloat() / durationMs).coerceIn(0f, 1f)
                val smooth = progress * progress * (3f - 2f * progress)  // ease-in-out
                val currentBpm = startBpm + (endBpm - startBpm) * smooth

                sequencer?.tempoInBPM = currentBpm

                delay(stepMs)
                if (!isActive) break
            }

            sequencer?.tempoInBPM = endBpm
            isInTempoChange = false
        }
    }

    private fun resetTempoToNormal() {
        if (sequencer?.tempoInBPM != targetBpm) {
//            println("Retour au tempo normal: ${sequencer?.tempoInBPM} → $targetBpm BPM")
            applyTempoChange(
                startBpm = sequencer?.tempoInBPM ?: targetBpm,
                endBpm = targetBpm,
                durationMs = 800L,
                tempoType = "Retour tempo"
            )
        }
    }
    private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
        val metadataList = sharedScreenModel.getFullMarkers()
        navigationSteps.clear()
        var lastSegno = 0
        var lastFactor = Dynamic.MF.factor
        val ritRegex = Regex("""rit\.?|ritard\.?|ritenuto\.?|ritardando""", RegexOption.IGNORE_CASE)
        val rallRegex = Regex("""rall\.?|rallent\.?|rallentando""", RegexOption.IGNORE_CASE)

        metadataList.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
            val currentIndex = gridIndex ?: 0

            val dsRegex = Regex("""D\.?S\.?""")
            val dcRegex = Regex("""D\.?C\.?""")
            val dsGPattern = Regex("""D\.?S\.?_GROUP_PART""")
            val dcGPattern = Regex("""D\.?C\.?_GROUP_PART""")

            val last_grid = sharedScreenModel.getTotalGridCount()
            val hairPins = sharedScreenModel.getHairPins()
            when {
                // segno
                marker.contains("$") -> {
                    lastSegno = currentIndex
                    println("Cible ($) mémorisée au $lastSegno")
                }
                // Point d'orgue
                marker.contains("\uD834\uDD10") -> {
                    val beat = if(note.contains('•')) 2 else 1 // demi-ton sur .) ou non
                    navigationSteps.add(
                        FMediaPlayer.NavigationStep(
                            marker,
                            currentIndex,
                            isHold = true,
                            beatInDC = beat
                        )
                    )
                    println("Point d'orgue (\uD834\uDD10) mémorisée au grille n° $gridIndex")
                }
                // Rit ...
                ritRegex.containsMatchIn(marker) -> {
                    val endGrid = if(lastCallerMarker == 0) last_grid else lastCallerMarker

                    navigationSteps.add(
                        NavigationStep(
                            marker = marker,
                            gridIndex = currentIndex,
                            isTempoChange = true,
                            tempoType = "rit",
                            endGridForTempo = endGrid,
                            targetTempoMultiplier = 0.55f // 55% du tempo initial
                        )
                    )
                    println("Ritardando mémorisé à grille $currentIndex jusqu'à $endGrid")
                }
                rallRegex.containsMatchIn(marker) -> {
                    val endGrid = last_grid

                    navigationSteps.add(
                        NavigationStep(
                            marker = marker,
                            gridIndex = currentIndex,
                            isTempoChange = true,
                            tempoType = "rall",
                            endGridForTempo = endGrid,
                            targetTempoMultiplier = 0.50f // 50% du tempo initial
                        )
                    )
                    println("Rallentando mémorisé à grille $currentIndex jusqu'à $endGrid")
                }
                // DS
                dsGPattern.matches(marker.trim())-> {
                    val target = if (lastSegno > 0) lastSegno else 0
                    var indx = if((last_grid - currentIndex) <= 0) {
                        currentIndex - 1
                    } else currentIndex
                    navigationSteps.add(
                        NavigationStep(
                            marker,
                            gridIndex = indx,
                            targetGrid = target,
                        )
                    )
                    println("Lien créé : $marker à grille n° $indx vers cible $target ........")
                }
                dsRegex.matches(marker.trim()) || marker == "DSFin" -> {
                    val target = if (lastSegno > 0) lastSegno else 0

                    navigationSteps.add(
                        NavigationStep(
                            marker,
                            currentIndex,
                            targetGrid =  target
                        ))
                    println("Lien DS créé : Saut immédiat à $gridIndex vers Segno $target")
                }

                // DC
                dcGPattern.matches(marker.trim()) -> {
                    val indx = if((last_grid - currentIndex) <= 0) {
                        currentIndex - 1
                    } else currentIndex
//                    println("monn index est $indx car dernier est $last_grid et curr $currentIndex")
                    navigationSteps.add(
                        NavigationStep(
                            marker,
                            indx,
                            targetGrid = 0
                        )
                    )
                    println("Lien créé : $marker à $indx vers cible 0 ")
                }
                (dcRegex.matches(marker.trim()) && !marker.contains("DC_GROUP_PART")) -> {
                    println("dernier grille $last_grid")
                    var indx = if((last_grid - currentIndex) <= 0) {
                        currentIndex /*- 1*/
                    } else currentIndex
                    navigationSteps.add(
                        NavigationStep(
                            marker,
                            indx,
                            targetGrid = 0
                        )
                    )
                    println("Lien DC créé : $marker à $indx vers le début")
                }

                // Farany
                marker.trim().equals("Farany_GROUP_PART", ignoreCase = true) -> {
                    val hasDcAfter = metadataList.any { (_, gi, _, _, mk, _, _, _) ->
                        (gi ?: 0) > currentIndex &&
                                (dcGPattern.matches(mk.trim()) ||
                                        dcRegex.matches(mk.trim()))
                    }
                    if (hasDcAfter) {
                        navigationSteps.add(
                            NavigationStep(
                                marker    = marker,
                                gridIndex = currentIndex,
                                targetGrid = last_grid,
                                isFin  = true,
                                finActive = false
                            )
                        )
                        println("Farany mémorisé à la grille $currentIndex")
                    }
                }
                // velocité
                extractDynamic(marker) != null && marker.trim() != "=" -> {
                    val dyn = extractDynamic(marker) ?: return@forEach
                    lastFactor = when (dyn) {
                        Dynamic.MF -> 1.0f
                        else       -> dyn.factor
                    }
                    navigationSteps.add(
                        NavigationStep(
                            marker = marker,
                            gridIndex = currentIndex,
                            dynamic = dyn
                        )
                    )
                    println("Dynamique '${dyn.label}' (vel=${dyn.velocity}) mémorisée à la grille $currentIndex")
                }
                // Soufflet
                marker.trim() == "<" || marker.trim() == ">" ||
                        marker.trim().contains("cres", ignoreCase = true) ||
                        marker.trim().contains("dim",  ignoreCase = true) -> {
                    val symbol = when {
                        marker.trim() == "<" -> '<'
                        marker.trim() == ">" -> '>'
                        marker.trim().contains("cres", ignoreCase = true) -> '<'
                        marker.trim().contains("dim",  ignoreCase = true) -> '>'
                        else -> return@forEach
                    }
                    val pair   = hairPins.find { it.startGrid == currentIndex } ?: return@forEach

                    val dynBefore = navigationSteps
                        .filter { it.dynamic != null && it.gridIndex <= currentIndex }
                        .maxByOrNull { it.gridIndex }?.dynamic ?: Dynamic.MF

                    val dynAfter = metadataList
                        .filter { (_, gi, _, _, mk, _, _, _) ->
                            (gi ?: 0) >= pair.endGrid && Dynamic.entries.any { d -> d.label == mk.trim() }
                        }
                        .minByOrNull { it.gridIndex ?: 0 }
                        ?.let { m -> Dynamic.entries.find { d -> d.label == m.marker.trim() } }

                    val explicitDynAfter = metadataList
                        .filter { (_, gi, _, _, mk, _, _, _) ->
                            (gi ?: 0) >= pair.endGrid &&
                                    extractDynamic(mk) != null
                        }
                        .minByOrNull { it.gridIndex ?: 0 }
                        ?.let { m -> extractDynamic(m.marker) }

                    val fromFactor = lastFactor
                    val toFactor   = when {
                        explicitDynAfter != null && symbol == '<' && explicitDynAfter.factor > fromFactor -> {
                            explicitDynAfter.factor
                        }
                        explicitDynAfter != null && symbol == '>' && explicitDynAfter.factor < fromFactor -> {
                            explicitDynAfter.factor
                        }
                        else -> nextDynamic(fromFactor, symbol)
                    }
                    lastFactor = toFactor

                    navigationSteps.add(
                        NavigationStep(
                            marker            = marker.trim(),
                            gridIndex         = currentIndex,
                            hairPin           = symbol,
                            hairPinEndGrid    = pair.endGrid,
                            hairPinFromFactor = fromFactor,
                            hairPinToFactor   = toFactor
                        )
                    )
                    println("NavigationStep HairPin '$symbol' : $currentIndex→${pair.endGrid} | ${fromFactor})→${toFactor})")
                }
            }
        }
    }
    private fun getPendingDcStep(): NavigationStep? {
        return navigationSteps.firstOrNull { step ->
            !step.alreadyDone &&
                    step.hairPin == null &&
                    step.dynamic == null &&
                    !step.isHold
                    && !step.isFin
        }
    }
    private fun startNavigationMonitor(sharedScreenModel: SharedScreenModel) {
        var dcDone = sharedScreenModel.getDcDone()
        val dsDone = sharedScreenModel.getDsDone()

        navigationJob?.cancel()
        navigationJob = playerScope.launch(Dispatchers.Default) {
            sharedScreenModel.activeIndex.collect { currentIndex ->

                val availableIndices = navigationSteps.map { it.gridIndex }
//                println("bpm:$targetBpm _ ${sequencer?.tempoInBPM}| Index en mémoire : $availableIndices")
//                println("i $currentIndex  ")

                if (sequencer?.isRunning == true) {
                    if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1) {
                        forceTempo(targetBpm.toDouble())
                    }
                    if (currentIndex < 0) return@collect

                    val step = navigationSteps.find {
                        it.gridIndex == currentIndex &&
                                !it.alreadyDone
                    }

                    if (step != null) {
                        if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1 && !isInTempoChange) {
                            forceTempo(targetBpm.toDouble())
                        }
                        when {
                            step.isFin -> {
                                if(step.finActive) {
                                    step.alreadyDone = true
//                                    println("Farany passage → FIN à grille $currentIndex")
                                    sequencer?.stop()
                                    sequencer?.tempoFactor = 1f
                                    synthetizer?.channels?.forEach {
                                        it?.controlChange(64, 0)
                                        it?.controlChange(123, 0)
                                    }
                                    onFinished()
                                }
                            }
                            // Point d'orgue
                            step.isHold -> {
                                val currentBpm = sequencer?.tempoInBPM ?: targetBpm
                                val beatMs = (60_000 / currentBpm).toLong()
                                val holdDuration = if (step.beatInDC == 2) beatMs / 2 else beatMs * 2

                                println("POINT D'ORGUE sur Grille $currentIndex | Durée: ${holdDuration}ms")

                                synthetizer?.channels?.forEach { it?.controlChange(64, 127) }
                                val previousFactor = sequencer?.tempoFactor ?: 1.0f
                                sequencer?.tempoFactor = 0.0001f

                                delay(holdDuration)

                                sequencer?.tempoFactor = previousFactor
                                synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
                            }

                            //Soufflet
                            step.hairPin != null -> {
                                val resolution = sequencer?.sequence?.resolution?.toLong() ?: 480L
                                val gridDistance = (step.hairPinEndGrid - step.gridIndex).coerceAtLeast(1)
                                val beatDurationMs = (60_000L / targetBpm).toLong()
                                val durationMs = (gridDistance * beatDurationMs).coerceIn(200L, 5000L)

                                println("HairPin '${step.hairPin}' grille ${step.gridIndex}→${step.hairPinEndGrid} | ${step.hairPinFromFactor}→${step.hairPinToFactor} | durée=${durationMs}ms")

                                applyCrescendo(
                                    fromFactor = step.hairPinFromFactor,
                                    toFactor   = step.hairPinToFactor,
                                    durationMs = durationMs
                                )
                            }

                            //Rit | Rall
                            step.isTempoChange -> {
                                step.alreadyDone = true
                                val currentBpm = sequencer?.tempoInBPM ?: targetBpm
                                val endBpm = targetBpm * step.targetTempoMultiplier
                                val gridDistance = (step.endGridForTempo - step.gridIndex).coerceAtLeast(1)
                                val beatDurationMs = (60_000L / targetBpm).toLong()
                                val durationMs = (gridDistance * beatDurationMs).coerceIn(1000L, 8000L)

//                                println("${step.tempoType.uppercase()} : Grille ${step.gridIndex} → ${step.endGridForTempo}")
//                                println("  ${currentBpm} BPM → $endBpm BPM sur ${durationMs}ms")

                                applyTempoChange(
                                    startBpm = currentBpm,
                                    endBpm = endBpm,
                                    durationMs = durationMs,
                                    tempoType = step.tempoType
                                )
                            }
                            //velocity
                            step.dynamic != null -> {
                                applyDynamic(step.dynamic)
                            }
                            else -> {
                                // $ dc ds ...
                                step.alreadyDone = true

                                val currentBpm = targetBpm
                                val beatDuration = (60_000 / currentBpm).toLong()
                                println("avant de sauter bpm=$targetBpm")

                                synthetizer?.channels?.forEach { it?.controlChange(64, 127) }
                                sequencer?.tempoFactor = 0.0001f
                                delay(beatDuration)

                                //                            println("et là je saut vers ${step.targetGrid}")
                                seekToGrid(step.targetGrid)
                                synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
                                sequencer?.tempoFactor = 1f

                                navigationSteps.filter { it.isFin && step.gridIndex > it.gridIndex }
                                    .forEach {
                                        it.finActive = true
                                        it.alreadyDone  = false
                                    }
                                if (sequencer?.isRunning == false) {
                                    sequencer?.tempoInBPM = targetBpm
                                    sequencer?.start()
                                }
                                //                            println("Après de sauter bpm=$targetBpm")
                            }
                        }
                    }
                }
            }
        }
    }
    private var syncJob: Job? = null

    private fun startSyncLoop(sharedScreenModel: SharedScreenModel) {
        syncJob?.cancel()
        syncJob = playerScope.launch(Dispatchers.Default) {
            while (isActive) {
                if (sequencer?.isRunning == true) {
                    val resolution = sequencer?.sequence?.resolution?.toLong() ?: 480L
                    val gridIndex = (sequencer!!.tickPosition / resolution).toInt()
                    sharedScreenModel.updateActiveIndexByIndex(gridIndex)
                }
                delay(20)
            }
        }
    }
    actual fun seekToGrid(gridIndex: Int) {
        val resolution = sequencer?.sequence?.resolution?.toDouble() ?: 480.0
        val tick = (gridIndex.toDouble() * resolution).toLong()
        sequencer?.tickPosition = tick

        applyBpm()
        // Voir les velocity avant
        val lastDynamic = navigationSteps
            .filter { it.dynamic != null && it.gridIndex <= gridIndex }
            .maxByOrNull { it.gridIndex }
            ?.dynamic ?: Dynamic.MF
        currentDynamicFactor = lastDynamic.factor
        applyVoiceStates()
    }

    actual fun play(){
        if (sequencer?.isOpen == true){
            applyBpm()
            sequencer?.start()
            println("La sequence vient d etre lancé ${sequencer?.isRunning}")
//            sequencer!!.open()
        }
    }
    actual fun pause(){
        sequencer?.stop()
    }
    actual fun requestSync(sharedScreenModel: SharedScreenModel) {
        val seq = sequencer?.sequence
        if (seq != null) {
            syncTuoWithMidi(seq, sharedScreenModel)
        }
    }

    actual fun syncNavigationMonitor(sharedScreenModel: SharedScreenModel) {
        this.boundModel = sharedScreenModel
        prepareNavigation(sharedScreenModel)
        startNavigationMonitor(sharedScreenModel)
        startSyncLoop(sharedScreenModel)
    }

    fun syncTuoWithMidi(sequence: Sequence, sharedScreenModel: SharedScreenModel) {
        val timestamps = mutableListOf<Long>()
        val resolution = sequence.resolution.toDouble()
        val bpm = targetBpm/*sequencer?.tempoInBPM?.toDouble() ?: 120.0*/
        val usPerTick = (60_000_000.0 / bpm) / resolution

        // détecter par 60 ticks càd par un temp (noir)
        val totalTicks = sequence.tickLength
        val step = 60L
        for (tick in 0..totalTicks step step) {
            val microsecond = (tick * usPerTick).toLong()
            timestamps.add(microsecond)
//            println("Note detectée au grille: ${tick/60} -> Temps: ${microsecond / 1000} ms")
        }

        // Détection de tous les note y compris les /2 /4 temps
        /*val processedTicks = mutableSetOf<Long>()

        for (track in sequence.tracks) {
            for (i in 0 until track.size()) {
                val event = track.get(i)
                val message = event.message
                if (message is ShortMessage *//*&& message.command == ShortMessage.NOTE_ON *//*&& message.data2 > 0) {
                    if (!processedTicks.contains(event.tick)) {
                        val microsecond = (event.tick * usPerTick).toLong()
                        timestamps.add(microsecond)
                        processedTicks.add(event.tick)
                        println("Note detectée au Tick: ${event.tick} -> Temps: ${microsecond / 1000} ms")
                    }
                }
            }
        }*/
        val sortedList = timestamps.sorted()
        sharedScreenModel.updateTimestamps(sortedList)
//        println("SUR tempo $targetBpm Synchronization terminer et mis à jour")
    }

    actual fun stop(){
        //        sequencer?.stop()
        sequencer?.microsecondPosition = 0
        navigationJob?.cancel()
        resetNavigationFlags()
        navigationSteps.clear()
        clearLoop()
        currentDynamicFactor = Dynamic.MF.factor
        resetTempoToNormal()
//        release()
    }
    actual fun getDuration(): Long {
        return (sequencer?.microsecondLength ?: 0L) / 1000
    }
    actual fun getCurrentPosition(): Long {
        return (sequencer?.microsecondPosition ?: 0L) / 1000
    }
    actual fun seekTo(position: Long) {
        sequencer?.microsecondPosition = position * 1000
        applyBpm()
    }
    actual fun release() {
        sequencer?.close()
        synthetizer?.close()
        playerScope.cancel()
    }
    actual fun setVolume(level: Float) {
        try {
            this.currentGlobalVolume = level
            applyVoiceStates()
            /*val volumeInt = (level * 127).toInt().coerceIn(0, 127)

            synthetizer?.channels?.forEachIndexed { index, channel ->
                if (index < 4) {
                    val vol = if (voiceVolumes[index] != 0f) volumeInt else 0
                    channel?.controlChange(7, vol)
                } else {
                    channel?.controlChange(7, volumeInt)
                }
            }*/
            val mixer = AudioSystem.getMixer(null)
            val lines = mixer.sourceLines
            for (line in lines) {
                if(line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                    val gainControl = line.getControl(FloatControl.Type.MASTER_GAIN) as FloatControl

                    val dB = (Math.log10(level.toDouble().coerceAtLeast(0.0001)) * 20).toFloat()
                    gainControl.value = dB.coerceIn(gainControl.minimum, gainControl.maximum)
                }
            }

        } catch (e: Exception){
            e.printStackTrace()
        }
//        println("la volume $level")
    }

    actual fun setPointA() {
        pointA = sequencer?.tickPosition ?: 0L
    }

    actual fun setPointB() {
        pointB = sequencer?.tickPosition ?: 0L
        if (pointB > pointA && pointA != -1L) {
            isLoopingAB = true
            startABMonitor()
        }
    }

    actual fun clearLoop() {
        isLoopingAB = false
        pointA = -1L
        pointB = -1L
    }
    private fun startABMonitor() {
        abJob?.cancel()
        abJob = playerScope.launch {
            while(isLoopingAB) {
                val currentTick = sequencer?.tickPosition?: 0L
                if (currentTick >= pointB) {
                    sequencer?.tickPosition = pointA
                }
                delay(50)
            }
        }
    }
    actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB)

    actual fun toggleVoice(index: Int) {
        saveVoicesVolumes()
        applyVoiceStates()
    }
    actual fun updateVoiceVolume(voiceIndex: Int, newVolume: Float) {
        if (voiceIndex in 0..3) {
            voiceVolumes[voiceIndex] = newVolume
            saveVoicesVolumes()
            applyVoiceStates()
        }
    }

    private fun applyVoiceStates() {
        try {
            synthetizer?.channels?.let { channels ->
                for (i in 0 until 4) {
                    if (i < channels.size) {
                        val userVol = voiceVolumes[i] / 127f
                        val globalVol = currentGlobalVolume

                        val finalVol = (127f * userVol * globalVol * currentDynamicFactor).toInt().coerceIn(0, 127)

//                    println("Voice[$i] user=${voiceVolumes[i]} global=$currentGlobalVolume dynFactor=$currentDynamicFactor → finalVol=$finalVol")

                        channels[i].controlChange(7, finalVol)
                        channels[i].controlChange(11, finalVol)

                        if (finalVol == 0) channels[i].controlChange(123, 0)
                        /*val targetVolume = voiceVolumes[i].toInt()

                        channels[i].controlChange(7, targetVolume)
                        channels[i].controlChange(11, targetVolume)

                        if (targetVolume == 0) {
                            channels[i].controlChange(123, 0)
                        }*/
                    }
//                    println("SATB $i Volumes: ${voiceVolumes[i]}")
                }
            }
        } catch (e: Exception) { e.printStackTrace() }
    }

    actual fun getVoiceVolumes(): List<Float> = voiceVolumes.toList()

    actual fun changeInstru(noInstru: Int) {
        val pgm = noInstru
        synthetizer?.channels?.let {
                channels ->
            for (i in 0 until 4) {
                channels[i].programChange(pgm)
            }
        }
    }
    actual fun getCurrentBPM(): Float {
        return targetBpm
    }

    actual fun setTempo(bpm: Float){
        this.targetBpm = bpm
        sequencer?.tempoInBPM = bpm
        boundModel?.let { modele ->
            val seq = sequencer?.sequence
            if (seq != null) {
                syncTuoWithMidi(seq, modele)
            }
            prepareNavigation(modele)
        }
        println("Tempo réglé à : $bpm BPM")
    }

    private fun saveVoicesVolumes() {
        val data = voiceVolumes.joinToString(",")
        prefs.put("voices_volumes", data)
    }

    private fun loadVoiceVolumes() {
        val data = prefs.get("voices_volumes", "127,127,127,127")
        val volumesArray = data.split(",")

        if (volumesArray.size == 4) {
            for (i in 0 until 4) {
                voiceVolumes[i] = volumesArray[i].toFloatOrNull() ?: 127f
            }
        }
    }
}