package mg.dot.feufaro.solfa

import SharedScreenModel
import kotlinx.coroutines.*
import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.SaveSettings
import mg.dot.feufaro.getGlobalTemplate
import mg.dot.feufaro.launchFilePicker
import mg.dot.feufaro.midi.MidiPitch
import mg.dot.feufaro.midi.MidiWriterKotlin
import mg.dot.feufaro.transformLyricsInput
import java.io.File
import kotlin.math.min

//@todo: split voices (ffpm19/ews22) ${S:mfs} in N4:, idem ffpm-212
//@todo: ffpm-164.txt O10:s3:mflswss // alternative notes for stanza 3 only
//@todo: ews-47.txt DS/$ + [12][3]
//@todo: ffpm-99-2.txt triolet.mid + triolet (m--)
//@todo: ffpm-657-1.txt ${m:4/4 Miadana kely}
//@todo: ews-80.txt ${m:12/4}
//@todo: ews-81.txt ${R=}${D:....}
//@todo: bug ffpm-629.txt sssszd au lieu de ssss-d(-rrrrtdr)
//@todo: bug ews-103.txt Triolet adds one blank
//@todo: bug ffpm-167-1.txt shoud start with : and not |

class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository: FileRepository) {
    private val T: MutableList<PTemplate> = mutableListOf()
    private val N: MutableList<POneVoiceNote> = mutableListOf()
    private val L: MutableList<POneStanzaLyrics> = mutableListOf()
    private val O: MutableMap<Int, MutableList<String>> = mutableMapOf()
    private val unparsedNote = mutableListOf<String>()
    private var refrainBeginsAt = -1
    private var refrainEndsAt = -1
    private var refrainMaxStanza = -1
    private var smartLyricsType = "L"
    private val pitches = mutableListOf<MidiPitch>()

    companion object {
        var currentFile = ""
        val REGEX_SHIFT_PAREN_UPWARD = Regex("([^a-yA-Y\\(\\[]+)([\\)\\]])")
        val REGEX_SHIFT_PAREN_DOWNWARD = Regex("([\\(\\[])([^a-yA-y\\)\\]]+)")
        val REGEX_COLLAPSE_PARENS_OPEN = Regex("([\\(\\[])[\\(\\[]+")
        val REGEX_COLLAPSE_PARENS_CLOSE = Regex("([\\)\\]])[\\)\\]]+")
        val REGEX_SINGLETON_PAREN = Regex("[\\(\\[](.[¹²³₁₂₃]?)?[\\)\\]]") // @todo
        val REGEX_SHIFT_COMMA_UPWARD = Regex("([^a-zA-Z]+)([',])")
        val REGEX_TEMPLATE_COMMENT = Regex("(\\$[A-Z]|\\$\\{[^\\}]*\\})")
        val REGEX_REPETITION = Regex("([zwdrmfslt-](?>[',]*))([1-9][0-9]*)", RegexOption.IGNORE_CASE)
        val REGEX_PARSE_META = Regex("\\|(?=[a-z]:)")
        val REGEX_LYRICS_COMMENT = Regex("\\$\\{([^\\}][^:]*:([^\\}]*))\\}")
        val REGEX_LYRICS_REPETITION = Regex("_(\\d+)")
        val REGEX_VOWELS_STAGE1 =
            Regex("[aeiouyòàéìỳ](?![,;\\.\\-:!\\?\\}»_\"]*([ aeiouyòàéìỳ/]|_[1-9]))", RegexOption.IGNORE_CASE)
        val REGEX_VOWELS_STAGE2 = Regex("(?<=[aeiouyòàéìỳ])_([,;\\.\\-:!\\?\\}»_\"]+)", RegexOption.IGNORE_CASE)
        val REGEX_VOWELS_STAGE3 = Regex("_([\\?\\!:,;\\.»\\)]+|(\\$\\{[^\\}]*\\})+)")
        val REGEX_MALAGASY_MN = Regex("([aeio])_([nm])([tdjkbp])")
        val REGEX_MALAGASY_MN_STAGE2 = Regex("_([mn])-")
        val REGEX_PAREN_RECURSIVE = Regex("(\\([^\\(\\)]*)\\(([^\\)]*)\\)")
        val REGEX_COMMENT = Regex("\\$\\{[^\\}]*\\}")
        val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}")
        val REGEX_NOTE_TOADD = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)>([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)")
        val REGEX_NOTE_TODROP = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)<")
    }

    var nextTIndex: Int = -1
    var nextNIndex: Int = -1
    var nextLIndex: Int = -1
    var inGroup: Boolean = false
    var templateString: String = ""

    private val meta: MutableMap<String, String> = mutableMapOf()
    private val lyricsComment: MutableList<String> = mutableListOf()
    suspend fun nextTimeUnitObject(accumulator: MutableList<TimeUnitObject>) {
        val lastTUO = accumulator.lastOrNull()
        nextTIndex++
        if (T.getOrNull(nextTIndex) == null) {
            withContext(Dispatchers.Main) {
                sharedScreenModel.updateFullTUOList(accumulator)
                //sharedScreenModel.doneTUOList()
            }
            return
        }
        /*if (nextTIndex == 5) {
            sharedScreenModel.doneTUOList()
        }
        if (T.getOrNull(nextTIndex) == null) {
            sharedScreenModel.doneTUOList()
            return
        }*/
        val pTemplate = T[nextTIndex]
        val unitObject = TimeUnitObject(pTemplate, lastTUO, N.size)
        if (lastTUO == null) {
            unitObject.setOldKey(sharedScreenModel.songKey.value)
        } else {
            unitObject.setOldKey(lastTUO.transposeOldKey)
        }
        val templateCharArray = pTemplate.template.toCharArray()
        var nextMarker: Char = '0'
        templateCharArray.forEach {  // D.-R
            when (it) {
                'D', 'R', 'M', 'F', 'S', 'L', 'T',
                'd', 'r', 'm', 'f', 's', 'l', 't',
                    -> {
                    nextNIndex++
                    if (!inGroup) {
                        nextLIndex++
                        if (L.size > nextLIndex) {
                            unitObject.addLyrics(L[nextLIndex])
                        }
                    }
                    N.forEachIndexed { voiceNumber, pOneVoiceNote ->
                        run {
                            unitObject.addNote(voiceNumber, pOneVoiceNote.oneVoiceNote, nextNIndex, nextMarker)
                        }
                    }
                    nextMarker = '0'
                }

                '.', ',' ->
                    unitObject.addMarker(it)

                '-', 'Z', 'z', 'w' ->
                    N.indices.forEach { voiceNumber ->
                        unitObject.addBlank(voiceNumber, it)
                    }

                '(', '[' -> {
                    nextLIndex++
                    if (L.size > nextLIndex) {
                        unitObject.addLyrics(L[nextLIndex])
                    }
                    inGroup = true
                }

                ')', ']' -> {
                    inGroup = false
                }
            }
        }
        withContext(Dispatchers.Main) {
            //sharedScreenModel.addTUO(unitObject)
            accumulator.add(unitObject)
        }
        nextTimeUnitObject(accumulator)
    }

    fun loadSolfa() {
        val sourceFileName: String = sharedScreenModel.currentPlayed()
        sharedScreenModel.reset()
        parse(sourceFileName)
    }

    fun loadNextInPlaylist() {
        val playlist = sharedScreenModel.playlist.value
        if (playlist.isNotEmpty()) {
            sharedScreenModel.playNext()
            loadSolfa()
        }
    }

    fun loadFile() {
        val screenModelScope= CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        val stateSettings = SaveSettings()

        screenModelScope.launch {
            val initialPath = stateSettings.loadLastUsedDir()
            val homedir = fileRepository.getAppPublicFolder().absolutePath

            launchFilePicker(
                mimeTypes = arrayOf("text/plain"),
                initialDirectory = if (initialPath.isNotBlank()) initialPath else homedir,
                onFileSelected = { path ->
                    if (path != null) {
                        screenModelScope.launch(Dispatchers.Default) {
                            sharedScreenModel.reset()
                            stateSettings.saveLastUsedDir(path)

                            try {
                                parse(path)
                            } catch (e: Exception) {
                                println("Erreur lors de l'ouverture du fichier : ${e.message}")
                            }
                        }
                    } else {
                        println("Pas de dichier")
                    }
                }
            )
        }
    }

    fun resetShared() {
        sharedScreenModel.reset()
    }
    fun parse(sourceFile: String) {
        //println("37: lFL bien reçu $sourceFile")
        currentFile = sourceFile
        val parseScope = CoroutineScope(Dispatchers.Default)
        parseScope.launch {
            val lines = try {
                fileRepository.readFileLines(sourceFile)
            } catch (e: Exception) {
                println("Opening $sourceFile raised exception {${e.message}")
                emptyList()
            }
            val contentString = fileRepository.readFileLines(sourceFile).joinToString("\n")
            sharedScreenModel.setFileContent(contentString, sourceFile)
            O.clear()
            T.clear()
            N.clear()
            L.clear()
            if (pitches.isNotEmpty()) {
                pitches[0].reset()
            }
            pitches.clear()
            refrainBeginsAt = -1
            refrainEndsAt = -1
            refrainMaxStanza = -1
            unparsedNote.clear()
            templateString = ""
            nextTIndex = -1
            nextNIndex = -1
            nextLIndex = -1
            lyricsComment.clear()
            if (sharedScreenModel.stanza.value == 0) {
                sharedScreenModel.setStanza(1)
            }
            sharedScreenModel.setNbStanzas(0)
            TimeUnitObject.hasMarker(false)
            lines.forEach { line ->
                run {
                    parseOneLine(line)
                }
            }
            unparsedNote.forEach {
                preloadN(it)
            }
            try {
                val localAccumulator = mutableListOf<TimeUnitObject>()
                withContext(Dispatchers.Main) {
                    sharedScreenModel.updateFullTUOList(emptyList())
                }
                nextTimeUnitObject(localAccumulator)
            } catch (e: Exception) {
                println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
            }
            val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
            val midiWriter = MidiWriterKotlin(fileRepository)
            midiWriter.process(pitches)
            midiWriter.save("whawyd3.mid")
            sharedScreenModel.setStanza(1)
        }
    }
    fun justBuild(sourceFile: String, sourceContent: String)  {
        currentFile = sourceFile
        val parseScope = CoroutineScope(Dispatchers.Default)
        parseScope.launch {
            val lines = try {
                fileRepository.readFileLines(sourceFile)
            } catch (e: Exception) {
                println("Opening $sourceFile raised exception {${e.message}")
                emptyList()
            }
            val contentString = fileRepository.readFileLines(sourceFile).joinToString("\n")
            sharedScreenModel.setFileContent(contentString, sourceFile)
            O.clear()
            T.clear()
            N.clear()
            L.clear()
            if (pitches.isNotEmpty()) {
                pitches[0].reset()
            }
            pitches.clear()
            refrainBeginsAt = -1
            unparsedNote.clear()
            templateString = ""
            nextTIndex = -1
            nextNIndex = -1
            nextLIndex = -1
            lyricsComment.clear()
            TimeUnitObject.hasMarker(false)
            lines.forEach { line ->
                run {
                    parseOneLine(line)
                }
            }
            unparsedNote.forEach {
                preloadN(it)
            }
            try {
                val localAccumulator = mutableListOf<TimeUnitObject>()
                withContext(Dispatchers.Main) {
                    sharedScreenModel.updateFullTUOList(emptyList())
                }
                nextTimeUnitObject(localAccumulator)
            } catch (e: Exception) {
                println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
            }
            val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
            val midiWriter = MidiWriterKotlin(fileRepository)
            midiWriter.process(pitches)
            midiWriter.save("whawyd3.mid")
        }
    }

    suspend fun saveTUOEdit(editState: TUOEditState) {
        println(editState)
        /* N.mapIndexed { index, note ->
             note.oneVoiceNote.mapIndexed { index, notes ->
                 println(notes.note)
             }
         }*/
        val filePath = currentFile
        if (filePath.isBlank()) return


        // inclusion -----
        val includedContent = expandInclusions(filePath)

        val lines = includedContent.split("\n").toMutableList()

        val targetIdx = editState.tuoIndex

        val t0Idx = lines.indexOfFirst { it.startsWith("T0:") }
        val u0Idx = lines.indexOfFirst { it.startsWith("U0:") }

        val idxS = lines.indexOfFirst { it.startsWith("N1") }
        val idxA = lines.indexOfFirst { it.startsWith("N2") }
        val idxT = lines.indexOfFirst { it.startsWith("N3") }
        val idxB = lines.indexOfFirst { it.startsWith("N4") }

        val noteS = if (idxS != -1) lines[idxS] else ""
        val noteA = if (idxA != -1) lines[idxA] else ""
        val noteT = if (idxT != -1) lines[idxT] else ""
        val noteB = if (idxB != -1) lines[idxB] else ""

        val currentStanza = sharedScreenModel.stanza.value

        val lyrIdx = lines.indexOfFirst { line ->
            line.contains(Regex("^[EY]$currentStanza:"))
        }

        val lyrics = lines[lyrIdx]

        val content = smartYLyrics(lyrics).substringAfter(":")

        val indexedLyrics = content.split('_', ' ', '/')
            .filter { it.isNotBlank() }
            .toMutableList()


        /*indexedLyrics.forEachIndexed { index, syllable ->
            println("Index $index : [$syllable]")
        }*/



        val measureString = sharedScreenModel.measure.value

        val templateString = when {
            t0Idx != -1 -> {
                lines[t0Idx]
            }
            u0Idx != -1 -> {
                lines[u0Idx]
            }
            else -> ""
        }

        val templateIndices = when {
            t0Idx != -1 -> {
                t0Idx
            }
            u0Idx != -1 -> {
                u0Idx
            }
            else -> 0
        }

        when {
            t0Idx != -1 -> {

            }

            u0Idx != -1 -> {
                val fullLine = lines[u0Idx]
//                println("=== DEBUG U0 ===")
//                println("fullLine = '$fullLine'")

                val afterU0 = fullLine.substringAfter("U0:")
                val blankPrefixRegex = Regex("^(.*?z)([0-9A-Z_+\\-]+)(:)")
                val match = blankPrefixRegex.find(afterU0)
                var blankPrefix: String = ""
                var body: String = ""
                if (match != null) {
                    body = afterU0.substring(blankPrefix.length)

                    if (editState.silentDuration == "-1") {
                        blankPrefix = match.value
                    }
                    else {
                        println("voici ${match.groupValues.joinToString(",")}")
                        val beforeDuration = match.groups[1]?.value ?: ""
                        val restOfSilenceBlock = match.groups[3]?.value ?: ":"

                        blankPrefix = "$beforeDuration${editState.silentDuration}$restOfSilenceBlock"
                    }
                    body = afterU0.substring(match.value.length)
                } else {
                    blankPrefix = afterU0.substringBefore(":") + ":"
                    body = afterU0.substringAfter(":")
                }

                val measure = measureString.split("/")[0].toIntOrNull() ?: 4

                val parser = ParseULine(body, measure)
                val symboles = parser.parsed()

                val newLigneT = "U0:"+blankPrefix+symboles
//                println("Ligne de U0 ==> $newLigneT")
                lines[u0Idx] = newLigneT

            }

            else -> {
                println("Aucune ligne T0 ou U0 trouvée dans le fichier")
                return
            }
        }

        val templatArray = templateToArray(if(u0Idx != -1) lines[u0Idx] else lines[t0Idx])
       /* println("index | contenuTUO")
        templatArray.forEachIndexed { index, block ->
            println("$index       $block")
        }*/


        val markerClean = editState.marker
        val modulaRegex = Regex("""^\s*D(ô|o)?\s*dia\s*(C|Db|D|Eb|E|F|Gb|G|Ab|A|Bb|B)\s*$""", RegexOption.IGNORE_CASE)
        val match = modulaRegex.find(markerClean)

        val newMarker = if (match != null) {
            val lettre = match.groupValues[2]
            "\${c:$lettre}"
        } else {
            markerClean.split(Regex("\\s+")).filter { it.isNotEmpty() }.joinToString(" ") { part ->
                when (part) {
                    "𝄐" -> "\$Q"
                    else -> "\${$part}"
                }
            }
        }
        /* MODIF SOURCE  Note&Lyrics   */
        val updatedSource = updateSourceLines(lines, templatArray, targetIdx, editState)

        /* MODIF TEMPLATE */
        val markerUpdated = updateMarkerSource(if(u0Idx != -1) lines[u0Idx] else lines[t0Idx], targetIdx, newMarker, editState)



        val templateUpd = reconstructIt(markerUpdated, measureString[0].digitToInt())

        println("Re-formater:: $templateUpd")

        //  RESTAURATION DES TEMPLATES
        if (templateString != "") {
            updatedSource[templateIndices] = templateUpd
        }

        val finalString = updatedSource.joinToString("\n")

        val tempDir = System.getProperty("java.io.tmpdir")?: ""
        val fileName = filePath.substringAfterLast('/')
        val currentTempFile = File(tempDir, fileName)
        currentTempFile?.deleteOnExit()


        val normalizedTempDir = File(tempDir).absolutePath
        val originalFilePath = if (filePath.startsWith(normalizedTempDir)) {
            filePath
        } else {
            ""
        }

//        println("Fichier Modifier:\n" +
//                "$finalString")
        //fileRepository.saveFile(filePath, finalString.toByteArray())

        currentTempFile?.writeBytes(finalString.toByteArray())

        withContext(Dispatchers.Main) {
            justBuild(currentTempFile.absolutePath, finalString)
        }
    }

    private suspend fun expandInclusions(filePath: String):String {
        val result = StringBuilder()
        val lastSlash = filePath.lastIndexOf('/')
        val directory = if (lastSlash != -1) filePath.substring(0, lastSlash + 1) else ""
        // Si inclusion multiples
        val myLines = try {
            fileRepository.readFileLines(filePath)
        } catch (e: Exception) {
            return "// Erreur lecture: $filePath"
        }

        val inclusionRegex = Regex("""I\d+:[^ \n\r]+""")

        myLines.forEach { line ->
            var lastIndex = 0
            val matches = inclusionRegex.findAll(line).toList()

            if (matches.isNotEmpty()) {
                val lineBuilder = StringBuilder()
                for (match in matches) {
                    lineBuilder.append(line.substring(lastIndex, match.range.first))

                    try {
                        val fullTag = match.value
                        val parts = fullTag.split(":", limit = 3)

                        if (parts.size >= 2) {
                            val fileName = parts[1]
                            val fullPath = directory + fileName
                            val rawIncludedContent = fileRepository.readFileLines(fullPath)

                            if (parts.size > 2 && parts[2].isNotEmpty()) {
                                val regexIgnoreString = parts[2]

                                val ignoredLinesRegex = try {
                                    Regex(regexIgnoreString)
                                } catch (e: Exception) {
                                    Regex("^:")
                                }

                                val filteredLines = rawIncludedContent.filter { incLine ->
                                    ignoredLinesRegex.find(incLine) == null
                                }

                                lineBuilder.append(filteredLines.joinToString("\n"))
                            } else {
                                lineBuilder.append(rawIncludedContent.joinToString("\n"))
                            }
                        }
                    } catch (e: Exception) {
                        lineBuilder.append("// Erreur inclusion: ${e.message}")
                    }
                    lastIndex = match.range.last + 1
                }
                lineBuilder.append(line.substring(lastIndex))
                result.append(lineBuilder.toString()).append("\n")
            } else {
                result.append(line).append("\n")
            }
        }
        return result.toString().trimEnd()
    }

    private fun updateSourceLines(
        lines: MutableList<String>,
        fragments: List<String>,
        targetIdx: Int,
        editState: TUOEditState
    ): MutableList<String> {
        /* Notes N1 N2 N3 N4 */
        updateNotesInLines(lines, fragments, targetIdx, editState)
        /* MODIF LYRICS */
        //updateLyricsInLines(lines, fragments, targetIdx, deleteCount, editState)
        return lines
    }

    private fun updateLyricsInLines(
        lines: MutableList<String>,
        fragments: List<String>,
        targetIdx: Int,
        editState: TUOEditState
    ) {
        editState.lyricsByStanza.forEach { (stanzaNum, newSyllable) ->
            val lineIdx = lines.indexOfFirst { it.matches(Regex("^[YE]$stanzaNum:.*")) }
            if (lineIdx == -1) return@forEach

            val currentLine = lines[lineIdx]
            val prefix = currentLine.substringBefore(":") + ":"
            val body = currentLine.substringAfter(":")

            val tokens = tokenizeLyricsPreserving(body)

            val textTokenIndices = tokens.indices.filter {
                !tokens[it].startsWith("\${") && tokens[it] != "/"
            }

//            println("--- TABLEAU DE DEBUG SYLLABES (Strophe $stanzaNum) ---")
//            println(String.format("%-5s | %-15s | %-10s", "Idx", "Token (Original)", "IsTarget"))
            tokens.forEachIndexed { i, token ->
                val isTarget = if (textTokenIndices.contains(i)) " (Syllabe)" else " (Structure)"
//                println(String.format("%-5d | %-15s | %-10s", i, token, isTarget))
            }

            if (targetIdx < textTokenIndices.size) {
                val idxToReplace = textTokenIndices[targetIdx]
//                println(">>> Remplacement index $idxToReplace (Cible $targetIdx) : '${tokens[idxToReplace]}' -> '$newSyllable'")
                tokens[idxToReplace] = newSyllable
            }

            val reconstructed = tokens.joinToString("")
            lines[lineIdx] = prefix + reconstructed
//            println("Résultat final : ${lines[lineIdx]}")
        }
    }

    private fun tokenizeLyricsPreserving(input: String): MutableList<String> {
        val result = mutableListOf<String>()
        val regex = Regex("""(\$\{.*?\})|(/)|([^\s/]+(?:\s+|))""")

        regex.findAll(input).forEach { match ->
            result.add(match.value)
        }
        return result
    }


    private fun updateNotesInLines(
        lines: MutableList<String>,
        fragments: List<String>,
        targetIdx: Int,
        editState: TUOEditState
    )   {
        // Ajout/Enlèvement grille de fin
        if (editState.tuoIndex == 9999) {
            AddOrRemoveGridActionOnNotes(lines, editState)
            return
        }
        val notesByVoice = editState.notesByVoice
        val originalNotes = editState.originalNotes
        val templateFragment = editState.templateFragment
        val pureTemplate = templateFragment.replace(Regex("[()]"), "")

        for (voiceNum in 0..3) {
            val linePrefix = "N${voiceNum + 1}:"
            val lineIdx = lines.indexOfFirst { it.startsWith(linePrefix) }

            if (lineIdx != -1) {
                val currentLine = lines[lineIdx]
                val prefix = currentLine.substringBefore(":") + ":"
                val noteBody = currentLine.substringAfter(":")
                val hasAnchor = noteBody.contains("#")
                val noteExpanded = expandNotes(noteBody.replace(Regex("\\s+"), ""))

//                println("Notes===>$noteExpanded")
                val newNot = notesByVoice[voiceNum] ?: ""
                val oldNot = originalNotes[voiceNum] ?: ""

                val isNoteModified = newNot != oldNot
//                println("est-ce? $isNoteModified = $newNot != $oldNot")

                if (!isNoteModified) {
//                    println("Voix $linePrefix : Aucune modification détectée (Note: $oldNot ).")
                    continue
                }

                val newNote = notesByVoice[voiceNum] ?: ""
                if (newNote.isEmpty()) continue
                val cleanNewNote = revertMusicalInput(newNote)


                val regex = Regex("#\\S[',]*|\\s#\\S[',]*|/|\\(|\\)|[drmfsltDRFSTzw][0-9'¹²³⁴⁵₁₂₃₄₅,]*|[-.―•]")
                val allTokens = regex.findAll(noteExpanded)
                    .map { it.value }
                    .filter { it != "." }
                    .toList()

                val targetPointer = getNotePointer(fragments, targetIdx, currentLine)

                val resultTokens = mutableListOf<String>()
                var currentLogicalIdx = 0
                var i = 0
                val consumedTokenIndices = mutableSetOf<Int>()

//                println("\nSuivi pour la voix $linePrefix (Pointeur cible: $targetPointer)")
//                println(
//                    String.format(
//                        "%-5s | %-10s | %-8s | %-12s | %-10s",
//                        "Idx P",
//                        "Token",
//                        "Type",
//                        "LogiqueIdx",
//                        "Action"
//                    )
//                )
                while (i < allTokens.size) {
                    if (consumedTokenIndices.contains(i)) {
                        val token = allTokens[i]
                        if (token.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
                            currentLogicalIdx++
                        }
                        i++
                        continue
                    }
                    val token = allTokens[i]
                    val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"

                    if (isStructural) {
//                        println(String.format("%-5d | %-10s | %-8s | %-12s | %-10s", i, token, "STRUCT", "-", "Keep"))
                        resultTokens.add(token)
                        i++
                        continue
                    }

                    if (currentLogicalIdx == targetPointer) {
                        val originalToken = allTokens[i]

//                        println("Insertition Multiples : $newNot")
                        val rawUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
                        /** Si y a '-' dans template inutile d' ajouter au notes */
                        val hasDashInTemplate = editState.templateFragment.contains("-")
                        val newUserTokens = if (hasDashInTemplate) {
                            rawUserTokens.filter { it.matches(Regex("[drmfsltDRFSTzw].*")) }
                        } else {
                            rawUserTokens
                        }
//                        val newUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
                        val oldUserTokens = regex.findAll(revertMusicalInput(oldNot)).map { it.value }.toList()

                        val firstNewNote = newUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
                        val firstOldNote = oldUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""

                        val (_, newLvl) = parseNoteAndOctave(firstNewNote)
                        val (_, oldLvl) = parseNoteAndOctave(firstOldNote)
                        val octaveDiff = newLvl - oldLvl


                        val oldTokensForThisFragment = regex.findAll(revertMusicalInput(oldNot))
                            .map { it.value }
                            .filter { it.matches(Regex("[drmfsltDRFSTzw].*")) }
                            .toList()
                        val dynamicDeleteCount = oldTokensForThisFragment.size


                        newUserTokens.forEach { ut ->
                            val dropRegex = Regex("${Regex.escape(ut)}<")
                            val isThisNoteDropped = dropRegex.containsMatchIn(revertMusicalInput(newNot))

                            if (isThisNoteDropped) {
                                if (ut == "z" || ut == "-" || ut == "―") {
//                                    println("Suppression de résonance/silence ($ut)...")
                                    var lookAheadIdx = i
                                    var foundDashIdx = -1

                                    while (lookAheadIdx < allTokens.size) {
                                        val lat = allTokens[lookAheadIdx]
                                        if ((lat.matches(Regex("[-―]")) || lat.startsWith("z")) && !consumedTokenIndices.contains(lookAheadIdx)) {
                                            foundDashIdx = lookAheadIdx
                                            break
                                        }
                                        lookAheadIdx++
                                    }
                                    if (foundDashIdx != -1) {
                                        consumedTokenIndices.add(foundDashIdx)
                                    }
                                } else {
//                                    println("Suppression de la note $ut (gérée par le saut du fragment d'origine)")
                                }
                            } else {
                                val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
                                    val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
                                    val (uBase, _) = parseNoteAndOctave(ut)
                                    uBase + formatOctave(fileLvl + octaveDiff)
                                } else {
                                    ut
                                }
//                                println("j'aoute $processedToken")

                                resultTokens.add(processedToken)

                                if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
//                                    println(
//                                        String.format(
//                                            "NEW   | %-10s | INSERT | %-12d | REPLACE",
//                                            processedToken,
//                                            currentLogicalIdx
//                                        )
//                                    )
                                    currentLogicalIdx++
                                }
                            }
                        }

                        var skipped = 0
                        while (skipped < dynamicDeleteCount && i < allTokens.size) {
                            if (consumedTokenIndices.contains(i)) {
                                i++
                                continue
                            }
                            val nextT = allTokens[i]
                            if (!nextT.contains("#") && nextT != "/" && nextT != "(" && nextT != ")") {
                                skipped++
                            } else if (hasAnchor) {
                                resultTokens.add(nextT)
                            }
                            i++
                        }
                        currentLogicalIdx++
                    } else {
                        // AFFICHAGE NOTE NORMALE
//                        println(
//                            String.format(
//                                "%-5d | %-10s | %-8s | %-12d | %-10s",
//                                i,
//                                token,
//                                "NOTE",
//                                currentLogicalIdx,
//                                "Keep"
//                            )
//                        )
                        resultTokens.add(token)
                        currentLogicalIdx++
                        i++
                    }
                }

                lines[lineIdx] = prefix + resultTokens.joinToString("")

//                println("-".repeat(55))
//                println("RÉSULTAT : ${lines[lineIdx]}")
            }
        }
    }

    private fun AddOrRemoveGridActionOnNotes(
        lines: MutableList<String>,
        editState: TUOEditState
    ) {
        for (voiceNum in 0..3) {
            val linePrefix = "N${voiceNum + 1}:"
            val lineIdx = lines.indexOfFirst { it.startsWith(linePrefix) }

            if (lineIdx != -1) {
                val currentLine = lines[lineIdx]
                val prefix = currentLine.substringBefore(":") + ":"
                val noteBody = currentLine.substringAfter(":")

                val regex = Regex("#\\S[',]*|\\s#\\S[',]*|/|\\(|\\)|[drmfsltDRFSTzw][0-9'¹²³⁴₁₂₃₄,]*|[-.―•]")
                val allTokens = regex.findAll(expandNotes(noteBody.replace(Regex("\\s+"), "")))
                    .map { it.value }
                    .filter { it != "." }
                    .toMutableList()

                /*if (editState.templateFragment == "-") {
                        var lastNoteIdx = -1
                        for (idx in allTokens.indices.reversed()) {
                            val token = allTokens[idx]
                            val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
                            if (!isStructural) {
                                lastNoteIdx = idx
                                break
                            }
                        }
                        if (lastNoteIdx != -1) {
                            allTokens.removeAt(lastNoteIdx)
                        }
                    } else {*/
                // +
                if (editState.templateFragment == "+") {
                    var lastNoteIdx = -1
                    for (idx in allTokens.indices.reversed()) {
                        val token = allTokens[idx]
                        val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
                        if (!isStructural) {
                            lastNoteIdx = idx
                            break
                        }
                    }

                    if (lastNoteIdx != -1) {
                        allTokens.add(lastNoteIdx + 1, "d")
                    } else {
                        allTokens.add("d")
                    }
                }

                lines[lineIdx] = prefix + allTokens.joinToString("")
            }
        }
        return
    }

    private fun updateMarkerSource(
        templateLine: String,
        targetPointer: Int,
        initMarkerValue: String,
        editState: TUOEditState
    ): String {
        /* Add or Remove lastGrid */
        if (editState.tuoIndex == 9999) {
            val finalResult = AddOrRemoveGridActionOnTemplate(templateLine, editState)
            return finalResult
        }


        /* Set Pending Hairpin */
        when (initMarkerValue) {
            "\${>}", "\${<}" -> sharedScreenModel.setPendingHairPin(true, targetPointer)
            "\${=}" -> sharedScreenModel.setPendingHairPin(false)
        }

        val notesByVoice = editState.notesByVoice
        val originalNotes = editState.originalNotes
        val separat = editState.sep
        val originalSeparat = editState.originalSep
        var anyNoteModified = false
        for (voiceNum in 0..3) {
            val newNot = notesByVoice[voiceNum] ?: ""
            val oldNot = originalNotes[voiceNum] ?: ""

            if (newNot != oldNot) {
                anyNoteModified = true
                break
            }
        }
        val isExplicitDeletion = (initMarkerValue == "\${_}")

        if((originalSeparat != separat) || isExplicitDeletion) {
            anyNoteModified = true
        }

        var newMarkerValue = if(isExplicitDeletion) "" else initMarkerValue

        if(!anyNoteModified && newMarkerValue.isEmpty() && (originalSeparat == separat)) {
//            println("Aucune modification détectée")
            return templateLine
        }
        val isT0 = templateLine.startsWith("T0")

        val prefix: String
        val body: String
        val suffix = if (isT0) "}" else ""

        if (isT0) {
            prefix = "T0:{"
            body = templateLine.substringAfter("{").substringBeforeLast("}")
        } else {
            val match = Regex("^(.*?z\\d|.*?z.):").find(templateLine)
            if (match != null) {
                prefix = match.value
                body = templateLine.substring(match.value.length)
            } else {
                prefix = templateLine.substringBefore(":") + ":"
                body = templateLine.substringAfter(":")
            }
        }

//        println("\n" + "=".repeat(100))
//        println("DEBUG COMPACTAGE & MARKERS - préfixe $prefix")
//        println("Cible Idx: $targetPointer | Nouveau Marqueur: \"$newMarkerValue\"")
//        println("-".repeat(100))
//        println(String.format("%-4s | %-25s | %-15s | %-25s", "Idx", "Bloc Étendu (Input)", "Action", "Bloc Compacté (Output)"))
//        println("-".repeat(100))

        val resultBody = StringBuilder()
        var currentLogicalIdx = 0
        var i = 0
        var currentBlock = StringBuilder()
        val separators = listOf(':', '|', '!', '/')
        var dropNextCloseParenthesis = false

        while (i <= body.length) {
            if (i < body.length && body[i] == '$' && i + 1 < body.length && body[i + 1] == '{') {
                val endPos = body.indexOf('}', i)
                if (endPos != -1) {
                    currentBlock.append(body.substring(i, endPos + 1))
                    i = endPos + 1
                    continue
                }
            }

            if (i == body.length || body[i] in separators) {
                if (isT0 && currentBlock.toString().trim().isEmpty()) {
                    if (i < body.length) resultBody.append(body[i])
                    currentBlock.clear()
                    i++
                    continue
                }
                if (i == body.length && currentBlock.toString().trim().isEmpty()) {
                    break
                }

                var rawBlockText = currentBlock.toString()
                var actionTaken = "Keep"

                if (rawBlockText.trim().isEmpty()) {
                    rawBlockText = if(isT0) "" else "z"
                    actionTaken = if(isT0) "EMPTY->\"\"" else "EMPTY->z"
                }

                var blockToProcess = if (currentLogicalIdx == targetPointer) {
                    actionTaken = if (newMarkerValue.isEmpty()) "DELETE" else "UPDATE"


                    /* On ne ré-insere pas la même marker*/
                    val markerRegex = Regex("""\$\{.*?\}|\$.""")
                    val blockWithoutOldMarkers = rawBlockText.replace(markerRegex, "")

                    val leadingCloseParentheses = blockWithoutOldMarkers.takeWhile { it == ')' }
                    val oldCleaned = blockWithoutOldMarkers.substring(leadingCloseParentheses.length)

                    val newTemplate = getGlobalTemplate(editState)
                    val oldHadOpen = oldCleaned.contains("(")
                    val newHasOpen = newTemplate.contains("(")


                    if (oldHadOpen && !newHasOpen) {
                        dropNextCloseParenthesis = true
                    }
                    var adjustedTemplate = newTemplate
                    if (newTemplate.endsWith(")")) {
                        val remainingText = body.substring(i).trimStart { it in separators }
                        if (remainingText.startsWith(")")) {
                            adjustedTemplate = newTemplate.dropLast(1)
                        }
                    }

                    val rawProcessed = separat + leadingCloseParentheses + newMarkerValue + adjustedTemplate

                    var cleanedProcessed = rawProcessed
                    while (cleanedProcessed.contains("((") || cleanedProcessed.contains("))")) {
                        cleanedProcessed = cleanedProcessed.replace("((", "(").replace("))", ")")
                    }

                    cleanedProcessed
                } else {
                    if (dropNextCloseParenthesis && rawBlockText.startsWith(")")) {
                        actionTaken = "FIX_ORPHEL_)"
                        dropNextCloseParenthesis = false
                        rawBlockText.substring(1)
                    } else {
                        rawBlockText
                    }
                }

                if (blockToProcess.contains("((") || blockToProcess.contains("))")) {
                    while (blockToProcess.contains("((") || blockToProcess.contains("))")) {
                        blockToProcess = blockToProcess.replace("((", "(").replace("))", ")")
                    }
                }
                val compactedBlock = StringBuilder()
                if (blockToProcess.isNotEmpty()) {
                    var k = 0
                    while (k < blockToProcess.length) {
                        val char = blockToProcess[k]

                        if (char == 'D' || char == 'z' || char == '-') {
                            var count = 1
                            val noteStart = char

                            val markerFound = StringBuilder()
                            var mIdx = k + 1
                            while (mIdx < blockToProcess.length) {
                                val nextChar = blockToProcess[mIdx]
                                if (nextChar == '$') {
                                    if (mIdx + 1 < blockToProcess.length && blockToProcess[mIdx + 1] == '{') {
                                        val end = blockToProcess.indexOf('}', mIdx)
                                        if (end != -1) {
                                            markerFound.append(blockToProcess.substring(mIdx, end + 1))
                                            mIdx = end + 1
                                            continue
                                        }
                                    } else if (mIdx + 1 < blockToProcess.length) {
                                        markerFound.append(blockToProcess.substring(mIdx, mIdx + 2))
                                        mIdx += 2
                                        continue
                                    }
                                }
                                break
                            }

                            var scanIdx = mIdx
                            while (scanIdx < blockToProcess.length && blockToProcess[scanIdx] == '-' && count < 4) {
                                if (scanIdx + 1 < blockToProcess.length && blockToProcess[scanIdx + 1] == '$') break
                                count++
                                scanIdx++
                            }

                            val compactCode = when (count) {
                                1 -> noteStart
                                2 -> if (noteStart == 'D') '8' else '2'
                                3 -> if (noteStart == 'D') 'C' else '3'
                                4 -> if (noteStart == 'D') 'G' else '4'
                                else -> noteStart
                            }

                            compactedBlock.append(compactCode).append(markerFound)
                            k = scanIdx
                        } else {
                            compactedBlock.append(char)
                            k++
                        }
                    }
                } else {
                    compactedBlock.append("")
                }

//                println(String.format("%-4d | %-25s | %-15s | %-25s",
//                    currentLogicalIdx, rawBlockText, actionTaken, compactedBlock.toString()))

                resultBody.append(compactedBlock)
                if (i < body.length) {
//                    println("$currentLogicalIdx == ${targetPointer - 1} && ($separat) && ${body[i]} in separators")
                    if (currentLogicalIdx == (targetPointer-1) && separat.isEmpty() && body[i] in separators) {
                        resultBody.append('|')
                    } else {
                        resultBody.append(body[i])
                    }
                }

                currentBlock.clear()
                currentLogicalIdx++
            } else {
                currentBlock.append(body[i])
            }
            i++
        }

        val cleannedBody = resultBody.toString().replace("//", "/")
        val finalResult = "$prefix$cleannedBody$suffix"
//        println("-".repeat(100))
//        println("RÉSULTAT FINAL COMPACTÉ : $finalResult")
//        println("=".repeat(100))

        return finalResult
    }

    private fun AddOrRemoveGridActionOnTemplate(templateLine: String, editState: TUOEditState): String {
        val prefix: String
        val body: String
        val suffix: String

        val isT0 = templateLine.startsWith("T0")
        if (isT0) {
            prefix = "T0:{"
            body = templateLine.substringAfter("{").substringBeforeLast("}")
            suffix = "}"
        } else {
            val match = Regex("^(.*?z\\d|.*?z.):").find(templateLine)
            if (match != null) {
                prefix = match.value
                body = templateLine.substring(match.value.length)
                suffix = ""
            } else {
                prefix = templateLine.substringBefore(":") + ":"
                body = templateLine.substringAfter(":")
                suffix = ""
            }
        }

        val separators = setOf(':', '|', '!', '/', ' ')
        val tokens = mutableListOf<String>()
        val isSeparator = mutableListOf<Boolean>()

        var temp = StringBuilder()
        for (char in body) {
            if (char in separators) {
                if (temp.isNotEmpty()) {
                    tokens.add(temp.toString())
                    isSeparator.add(false)
                    temp = StringBuilder()
                }
                tokens.add(char.toString())
                isSeparator.add(true)
            } else {
                temp.append(char)
            }
        }
        if (temp.isNotEmpty()) {
            tokens.add(temp.toString())
            isSeparator.add(false)
        }

        val updatedBody = if (editState.templateFragment == "-") {
            //-
            var lastNoteBlockIdx = -1
            for (idx in tokens.indices.reversed()) {
                if (!isSeparator[idx]) {
                    lastNoteBlockIdx = idx
                    break
                }
            }

            if (lastNoteBlockIdx != -1) {
                tokens.removeAt(lastNoteBlockIdx)
                isSeparator.removeAt(lastNoteBlockIdx)
            }

            val cleanTokens = mutableListOf<String>()
            var lastWasSeparator = false
            for (i in tokens.indices) {
                val token = tokens[i]
                val isSep = isSeparator[i]
                if (isSep) {
                    if (!lastWasSeparator) {
                        cleanTokens.add(token)
                        lastWasSeparator = true
                    } else {
                        val prevSep = cleanTokens.last()
                        if (prevSep == ":" && token != ":") {
                            cleanTokens[cleanTokens.lastIndex] = token
                        }
                    }
                } else {
                    cleanTokens.add(token)
                    lastWasSeparator = false
                }
            }
            cleanTokens.joinToString("")
        } else {
            // +
            var lastNoteBlockIdx = -1
            for (idx in tokens.indices.reversed()) {
                if (!isSeparator[idx]) {
                    lastNoteBlockIdx = idx
                    break
                }
            }

            if (lastNoteBlockIdx != -1) {
                if (lastNoteBlockIdx < tokens.lastIndex && isSeparator[lastNoteBlockIdx + 1]) {
                    val nextSepIdx = lastNoteBlockIdx + 1
                    val sep = tokens[nextSepIdx]
                    tokens.add(nextSepIdx + 1, "D")
                    isSeparator.add(nextSepIdx + 1, false)
                } else {
                    tokens.add("!")
                    isSeparator.add(true)
                    tokens.add("D")
                    isSeparator.add(false)
                }
            } else {
                tokens.add("D")
                isSeparator.add(false)
            }
            tokens.joinToString("")
        }

        val finalResult = "$prefix$updatedBody$suffix"

        //            println("=".repeat(100))
        //            println("EDITION FIN GRILLE (9999) : Action = ${editState.templateFragment}")
        //            println("RESULTAT COMPACTÉ (9999) : $finalResult")
        //            println("=".repeat(100))
        return finalResult
    }

    private fun parseNoteAndOctave(s: String): Pair<String, Int> {
        val base = s.replace(Regex("[^drmfsltDRFSTzw]"), "")
        var level = 0

        level += s.count { it == '\'' || it == '¹' || it == '²' || it == '³' }
        level -= s.count { it == ',' || it == '₁' || it == '₂' || it == '₃' }
        return Pair(base, level)
    }

    private fun formatOctave(level: Int): String {
        if (level == 0) return ""
        return if (level > 0) "'".repeat(level) else ",".repeat(Math.abs(level))
    }

    private fun revertMusicalInput(input: String): String {
        return input
            .replace("• ,", "")
            .replace("•", "")
            .replace("―", "-")
            .replace("•", "")
            .replace("³", "'''")
            .replace("₃", ",,,")
            .replace("²", "''")
            .replace("₂", ",,")
            .replace("¹", "'")
            .replace("₁", ",")

            .replace("di", "D")
            .replace("ri", "R")
            .replace("fi", "F")
            .replace("si", "S")
            .replace("ta", "T")

            .replace("(","")
            .replace(")","")
    }

    private fun parseNoteLineToArray(noteLine: String): List<String> {
        val content = noteLine.substringAfter(":").replace(" ", "")

        val result = mutableListOf<String>()
        var i = 0

        while (i < content.length) {
            val char = content[i]

            if (char == '/' || char == ' '/* || char == 'z'*/) {
                i++
                continue
            }
            if (char == '#') {
                i += 2
                continue
            }

            if (char.isLetter() || char == '-') {
                val noteBuilder = StringBuilder(char.toString())
                var nextIdx = i + 1

                while (nextIdx < content.length && (content[nextIdx] == ',' || content[nextIdx] == '\'')) {
                    noteBuilder.append(content[nextIdx])
                    nextIdx++
                }

                val fullNote = noteBuilder.toString()

                if (nextIdx < content.length && content[nextIdx].isDigit()) {
                    val repeatCount = content[nextIdx].toString().toInt()
                    repeat(repeatCount) {
                        result.add(fullNote)
                    }
                    nextIdx++
                } else {
                    result.add(fullNote)
                }

                i = nextIdx
                continue
            }

            i++
        }

        return result
    }

    private fun templateToArray(templateLine: String): List<String> {
        var templateArray: List<String> = emptyList()

        val templateType = if(templateLine.startsWith("T0")) "T" else "U"
        if(templateType == "T") {
            var content = templateLine.substringAfter("{").substringBeforeLast("}")
//            println("AVANT T: $content")

            content = content.replace(Regex("\\\$\\{.*?\\}"), "")

            val rawBlocks = content.split(Regex("[:|/!]"))

            templateArray = rawBlocks.filter { it.isNotBlank() }
//            println("\n\nLine = $templateLine \n," +
//                    "Array = ${templateArray.joinToString("|")}")
//            println("APRèS T: ${templateArray.joinToString("|")}")
        } else {
            val body = templateLine.replaceFirst(Regex("^U0:(?:z[0-9A-Z]:)?"), "")

            //println("Mon contenu est: $body.")
            val result = mutableListOf<String>()
            var currentBlock = StringBuilder()

            var i = 0
            while (i < body.length) {
                val char = body[i]

               if (char == '$' && i + 1 < body.length && body[i + 1] == '{') {
                    val closingBrace = body.indexOf('}', i)
                    if (closingBrace != -1) {
                        i = closingBrace + 1
                        continue
                    }
                }
                if (char == '$') {
                    i += 2
                    continue
                }

                if (char in listOf(':', '|', '!', '/')) {
                    if (currentBlock.isNotEmpty()) {
                        result.add(currentBlock.toString())
                        currentBlock.clear()
                    }
                }
                else if (char == ' ') {

                }
                else {
                    currentBlock.append(char)
                }

                i++
            }

            if (currentBlock.isNotEmpty()) result.add(currentBlock.toString())

            templateArray = result
        }

//            println("\n\nLine = $templateLine \n," +
//                    "Array = ${templateArray.joinToString("|")}")
        return templateArray
    }


    private fun encodeDuration(duration: Int): String {
        return when (duration) {
            1 -> "1";
            2 -> "2";
            3 -> "3";
            4 -> "4"
            6 -> "6";
            8 -> "8";
            10 -> "A";
            11 -> "B";
            12 -> "C";
            14 -> "E";
            16 -> "G";
            20 -> "K";
            24 -> "O";
            28 -> "S";
            32 -> "W"
            else -> duration.toString()
        }
    }
    private fun reconstructIt(finalResult: String, measure: Int): String {
        if(finalResult.startsWith("T0:")) {
            /* T0 */
            return finalResult
        } else {
            /* U0 */
            val prefixMatch = Regex("^(.*?z[048CEKO]:|.*?z.:)").find(finalResult)
            val prefix = prefixMatch?.value ?: "U0:z0:"
            val body = finalResult.substring(prefixMatch?.value?.length ?: (finalResult.indexOf(":") + 1))

            val buff = mutableListOf<String>()
            var currentBlockBuilder = StringBuilder()
            var idx = 0
            while (idx < body.length) {
                if (body[idx] == '$' && idx + 1 < body.length && body[idx + 1] == '{') {
                    val endPos = body.indexOf('}', idx)
                    if (endPos != -1) {
                        currentBlockBuilder.append(body.substring(idx, endPos + 1))
                        idx = endPos + 1
                        continue
                    }
                }

                val char = body[idx]
                if (char == ':' || char == '|' || char == '!') {
                    if (currentBlockBuilder.isNotEmpty()) {
                        buff.add(currentBlockBuilder.toString())
                        currentBlockBuilder.setLength(0)
                    }
                } else if (char == '/') {
                    currentBlockBuilder.append(char)
                    buff.add(currentBlockBuilder.toString())
                    currentBlockBuilder.setLength(0)
                } else {
                    currentBlockBuilder.append(char)
                }
                idx++
            }
            if (currentBlockBuilder.isNotEmpty()) {
                buff.add(currentBlockBuilder.toString())
            }

            val buff2 = mutableListOf<String>()
            val consumedGlobally = mutableSetOf<Pair<Int, Int>>()
            val consumedBlocks = mutableSetOf<Int>()

            fun getDurationAt(targetStr: String, targetCharIdx: Int, blockIdx: Int): Int {
                val cleanStr = targetStr.replace(Regex("\\$\\{.*?\\}|\\$."), "")
                val charAtIdx = targetStr[targetCharIdx]

                if (blockIdx > 0) {
                    val prevBlock = buff[blockIdx - 1].replace(Regex("\\$\\{.*?\\}|\\$."), "")
                    val isFirstActiveChar = targetCharIdx == 0 || targetStr.substring(0, targetCharIdx).all {
                        "., !:|(){}$".contains(it) || consumedGlobally.contains(blockIdx to targetStr.indexOf(it))
                    }

                    if (prevBlock.endsWith("-.") || prevBlock == ".") {
                        if (isFirstActiveChar) {
                            if (charAtIdx == 'z' || charAtIdx == 'D') {
                                return 4
                            }
                        }
                    }

                    if (!cleanStr.contains(".") && !cleanStr.contains(",")) {
                        if (prevBlock.endsWith("./") || prevBlock.endsWith(".")) return 2
                        if (prevBlock.endsWith(",/") || prevBlock.endsWith(",")) return 1
                    }
                }

                if (charAtIdx == 'D' || charAtIdx == 'z') {
                    val textAfter = targetStr.substring(targetCharIdx + 1)
                    val cleanAfter = textAfter.replace(Regex("\\$\\{.*?\\}|\\$."), "")
                    if (cleanAfter.startsWith(".") && !cleanAfter.substring(1).any { it == 'D' || it == 'z' || it == '-' }) {
                        return 4
                    }
                }

                if (!cleanStr.contains(".") && !cleanStr.contains(",")) return 4

                val pByPoint = cleanStr.split(".")
                val prefixBefore = targetStr.substring(0, targetCharIdx).replace(Regex("\\$\\{.*?\\}|\\$."), "")
                val cleanIdx = prefixBefore.length

                var curPos = 0
                var zoneIdx = 0
                for (i in pByPoint.indices) {
                    if (cleanIdx >= curPos && cleanIdx < curPos + pByPoint[i].length + 1) {
                        zoneIdx = i
                        break
                    }
                    curPos += pByPoint[i].length + 1
                }

                return if (cleanStr.contains(",")) {
                    if (pByPoint[zoneIdx].contains(",")) 1 else 2
                } else {
                    2
                }
            }

            var k = 0
            while (k < buff.size) {
                if (consumedBlocks.contains(k)) {
//                    println("Pour $k = ${buff[k]} -> Saut du bloc entier (déjà absorbé dans un silence cumulé)")
                    k++
                    continue
                }

                val currentStr = buff[k]
                val sb = StringBuilder()
                val prefixBuffer = StringBuilder()

//                println("Pour $k = $currentStr")

                if (k > 0) {
                    val prevBlock = buff[k - 1].replace(Regex("\\$\\{.*?\\}|\\$."), "")
                    val cleanCurrent = currentStr.replace(Regex("\\$\\{.*?\\}|\\$."), "")

                    if (prevBlock.endsWith("-./") || prevBlock.endsWith("-.)/")) {
                        if (cleanCurrent.contains(".") && cleanCurrent.startsWith("D") && cleanCurrent.count { it == 'D' } >= 2) {
                            val silenceDuration = 2
                            sb.append("z$silenceDuration ")
//                            println("      -> [RÈGLE SPÉCIALE] Silence résiduel injecté ! Insertion de 'z$silenceDuration '")
                        }
                    }
                }

                var charIdx = 0
                while (charIdx < currentStr.length) {
                    if (consumedGlobally.contains(k to charIdx)) {
//                        println("      -> Saut du caractère '${currentStr[charIdx]}' (déjà absorbé)")
                        charIdx++
                        continue
                    }

                    val char = currentStr[charIdx]
                    when {
                        char == '$' && charIdx + 1 < currentStr.length && currentStr[charIdx + 1] == '{' -> {
                            val endIdx = currentStr.indexOf('}', charIdx)
                            if (endIdx != -1) {
                                prefixBuffer.append(currentStr.substring(charIdx, endIdx + 1))
                                charIdx = endIdx
                            }
                        }

                        char == '$' && charIdx + 1 < currentStr.length -> {
                            prefixBuffer.append(char).append(currentStr[charIdx + 1])
                            charIdx++
                        }

                        char == '(' -> prefixBuffer.append(char)
                        char == ')' || char == '/' -> sb.append(char)

                        char == 'D' || char == 'z' -> {
                            var totalDuration = getDurationAt(currentStr, charIdx, k)
//                            println("      -> Note '$char' détectée. Zone initiale, Durée: $totalDuration")

                            var lookK = k
                            var lookC = charIdx + 1
                            var searching = true
                            var crossedParenthesis = false

                            while (searching) {
                                if (lookC >= buff[lookK].length) {
                                    lookK++; lookC = 0
                                    if (lookK >= buff.size) break
                                }

                                val next = buff[lookK][lookC]

                                when {
                                    next == '$' -> {
                                        if (lookC + 1 < buff[lookK].length && buff[lookK][lookC + 1] == '{') {
                                            val end = buff[lookK].indexOf('}', lookC)
                                            lookC = if (end != -1) end + 1 else lookC + 2
                                        } else {
                                            lookC += 2
                                        }
                                    }
                                    next == '-' -> {
                                        val resDur = getDurationAt(buff[lookK], lookC, lookK)
                                        totalDuration += resDur
                                        consumedGlobally.add(lookK to lookC)
//                                        println("         + Absorption tiret à [$lookK,$lookC]. Durée zone: $resDur. Total: $totalDuration")
                                        lookC++
                                    }

                                    next == '(' || next == ')' -> {
                                        crossedParenthesis = true
                                        lookC++
                                    }
                                    next == 'z' && lookK == k && char == 'D' -> {
                                        if (crossedParenthesis) {
                                            searching = false
                                        } else {
                                            var hasFutureResonance = false
                                            if (k + 1 < buff.size) {
                                                if (buff[k + 1].replace(Regex("\\$\\{.*?\\}|\\$."), "")
                                                        .startsWith("-")
                                                ) hasFutureResonance = true
                                            }
                                            if (!hasFutureResonance && currentStr.contains(".") && currentStr.contains(",")) {
                                                val resDur = getDurationAt(currentStr, lookC, k)
                                                totalDuration += resDur
                                                consumedGlobally.add(lookK to lookC)
                                                lookC++
                                            } else {
                                                searching = false
                                            }
                                        }
                                    }

                                    "., !:|(){}".contains(next) || next == '$' -> lookC++
                                    else -> searching = false
                                }
                            }

                            val isPureSilenceBlock =
                                char == 'z' && (lookK > k || (lookK == k && lookC >= buff[k].length))

                            if (isPureSilenceBlock) {
                                var nextBlockIdx = k + 1
                                var lookingForSilence = true

                                while (lookingForSilence && nextBlockIdx < buff.size) {
                                    val nextBlockRaw = buff[nextBlockIdx]
                                    val nextBlockClean = nextBlockRaw.replace(Regex("\\$\\{.*?\\}|\\$."), "").trim()

                                    if (nextBlockClean.isNotEmpty() && nextBlockClean.all {
                                            it == 'z' || "., !:|()/{}".contains(
                                                it
                                            )
                                        }) {
                                        val zIdxInNext = nextBlockRaw.indexOf('z')
                                        if (zIdxInNext != -1) {
                                            val nextSilenceDur = getDurationAt(nextBlockRaw, zIdxInNext, nextBlockIdx)
                                            totalDuration += nextSilenceDur
//                                            println("         [CUMUL SILENCE] -> Bloc $nextBlockIdx ('$nextBlockClean') absorbé. Durée ajoutée: $nextSilenceDur. Total temporaire: $totalDuration")
                                            consumedBlocks.add(nextBlockIdx)
                                            nextBlockIdx++
                                        } else {
                                            lookingForSilence = false
                                        }
                                    } else {
                                        lookingForSilence = false
                                    }
                                }
                            }

                            val code = if (char == 'z') {
                                "z${encodeDuration(totalDuration)}"
                            } else {
                                encodeDuration(totalDuration)
                            }

                            sb.append(prefixBuffer.toString())
                            prefixBuffer.setLength(0)
                            sb.append(code)
//                            println("      -> Code généré: $code")
                        }
                    }
                    charIdx++
                }

                if (prefixBuffer.isNotEmpty()) sb.insert(0, prefixBuffer.toString())
                var resultBlock = sb.toString()

                // TRIOLET ---
                val trioletRegex = Regex("""(?:\u0024T)?4(?:\u0024T)?4(?:\u0024T)?4(?:\u0024T)?""")

//                println("Mon actuel resBlk $resultBlock et ça contient? ${trioletRegex.containsMatchIn(resultBlock)} mon regex ${trioletRegex}")
                if (trioletRegex.containsMatchIn(resultBlock)) {
                    resultBlock = resultBlock.replace(trioletRegex) { _ ->
                        "t"
                    }
//                    println("      -> [RÈGLE TRIOLET] Motif absorbé et converti en 't'")
                }

                if (resultBlock.contains("31")) {
                    resultBlock = resultBlock.replace("31", "y")
//                    println("      -> Motif 'y' détecté")
                }

//                println("Pour ça ON a $resultBlock \n")
                if (resultBlock.isNotEmpty() || buff[k].contains("/")) buff2.add(resultBlock)
                k++
            }

            val cleanedBlocks = buff2.map { it.replace(" ", "") }
            /*val durationModifierRegex = Regex("^(.*?z)[0-9A-Z_+\\-](:|/)")
            val match = durationModifierRegex.find(prefix)

            val newPrefix = if (match != null) {
                val beforeDuration = match.groups[1]?.value ?: ""
                val separator = match.groups[2]?.value ?: ":"
                "$beforeDuration${editState.silentDuration}$separator"
            } else prefix*/

            val finalLines = (prefix + cleanedBlocks.joinToString(""))
                .replace("2z11", "y")
                .replace("/", "/ ")
                .replace(Regex("\\s+"), " ")
                .trim()

//            println(finalLines)
            return finalLines
        }
    }

    private fun expandNotes(text: String): String {
        val regex = Regex("([drmfsltDRFSTzw])([,']*)(\\d+)")
        return regex.replace(text) { matchResult ->
            val note = matchResult.groupValues[1]
            val octaves = matchResult.groupValues[2]
            val count = matchResult.groupValues[3].toInt()

            (note + octaves).repeat(count)
        }
    }

    private fun rearrangeNote(noteString: String, infiniteIter: Int = 0): String {
        var result: String = noteString
        result = result.replace(REGEX_PAREN_RECURSIVE, "$1$2")
        result = result.replace(REGEX_PAREN_RECURSIVE, "$1$2")
        result = result.replace(REGEX_PAREN_RECURSIVE, "$1$2")
        result = result.replace(REGEX_SHIFT_PAREN_UPWARD, "$2$1")
        result = result.replace(REGEX_SHIFT_PAREN_DOWNWARD, "$2$1")
        result = result.replace(REGEX_COLLAPSE_PARENS_OPEN, "$1")
        result = result.replace(REGEX_COLLAPSE_PARENS_CLOSE, "$1")
        result = result.replace(REGEX_SINGLETON_PAREN, "$1")
        result = result.replace(REGEX_SHIFT_COMMA_UPWARD, "$2$1")
        if (result != noteString && infiniteIter < 75) {
            result = rearrangeNote(result, infiniteIter + 1)
        }
        if (infiniteIter >= 70) {
            println("$infiniteIter\n$noteString\n$result")
        }
        return result
    }

    private fun preloadN(noteLine: String) {
        val voiceNumber = noteLine.substring(0, 1).toInt()
        val templateComments = mutableListOf<String>()
        val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString) { matchResult ->
            templateComments.add(matchResult.value)
            "&"
        }
        fun insertZToCharArray(input: String): CharArray {
            val delim = "/:!|"
            val reDelim = "[$delim](?=[$delim])".toRegex()
            val insertedZ = input.replace(reDelim) { it.value + "z"}
            return insertedZ.toCharArray()
        }
        val templateCharArray = insertZToCharArray(templateStripped)
        val noteLine = noteLine.replace(REGEX_TEMPLATE_COMMENT, "")
            .replace(" ", "")
        val lineRepeated = REGEX_REPETITION.replace(noteLine.substring(1)) { matchResult ->
            val (charIterated, iteration) = matchResult.destructured
            val nTimes = iteration.toInt()
            charIterated.repeat(nTimes)
        }.replace(" ", "")
        var noteChar2 = ""
        var anchor = "d"
        var inAnchor = false
        var anchorNote = ' '
        lineRepeated.toCharArray().forEach {
            if (inAnchor) {
                if (anchor.isEmpty()) {
                    anchorNote = it
                }
                if (anchor.isNotEmpty() && it !in listOf(',', '\'')) {
                    inAnchor = false
                } else {
                    anchor += it
                    return@forEach
                }
            }
            if (it == '#') {
                inAnchor = true
                anchor = ""
                return@forEach
            }
            noteChar2 += it
            val anchorSuffix = anchor.replace(Regex("^[drmfslt]"), "")
            if (it in listOf('d', 'r', 'm', 'f', 's', 'l', 't', 'D', 'R', 'M', 'F', 'S', 'L', 'T')) {
                noteChar2 += anchorSuffix
                if (
                    (anchorNote == 'r' && it in listOf('d', 'D'))
                    || (anchorNote == 'm' && it in listOf('d', 'D', 'r', 'R'))
                    || (anchorNote == 'f' && it in listOf('d', 'D', 'r', 'R', 'm', 'M'))
                    || (anchorNote == 's' && it !in listOf('s', 'S', 'l', 'L', 't', 'T'))
                    || (anchorNote == 'l' && it !in listOf('l', 'L', 't', 'T'))
                    || (anchorNote == 't' && it !in listOf('t', 'T'))
                ) {
                    noteChar2 += '\''
                }
            }
        }
        noteChar2 = noteChar2.replace(",'", "")
            .replace("',", "")
            .replace(",'", "")
            .replace("',", "")

        val noteCharIterator = noteChar2.replace("/", "").toCharArray().iterator()
        var result = ""
        var firstInLoop = true
        var replacement: Char = 'x'
        templateCharArray.forEach {
            when (it) {
                'D', 'R', 'M', 'F', 'S', 'L', 'T',
                'd', 'r', 'm', 'f', 's', 'l', 't',
                    -> {
                    if (firstInLoop) {
                        replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
                        firstInLoop = false
                    }
                    if (replacement == '(') {
                        result += replacement
                        replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
                    }
                    for (repetition in 0 until 6) {
                        result += replacement
                        replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
                        if (replacement in setOf(
                                'd', 'r', 'm', 'f', 's', 'l', 't', 'z', '(',
                                'D', 'R', 'M', 'F', 'S', 'L', 'T', '-', 'w', 'Z'
                            )
                        ) {
                            break
                        }
                    }
                }

                'w', '-' -> result += "~"
                'z', 'Z' -> result += "@"
                ',' -> result += ";"
                else -> result += it
            }
        }
        result = result.replace("w", "-")
        result = rearrangeNote(result)
        loadN(voiceNumber, result, templateComments)
    }

    private suspend fun parseOneLine(line: String, regexIgnoreString: String = "") {
        val index: Int
        val value: String
        val lineLength = line.trim().length
        if (lineLength <= 3) {
            return
        }
        val key: String = line.trim().substring(0, 1)
        if (lineLength > 4 && line.trim().substring(3, 4) == ":") {
            index = line.substring(1, 3).toIntOrNull() ?: 0
            value = line.trim().substring(4)
        } else {
            index = line.substring(1, 2).toIntOrNull() ?: 0
            value = line.trim().substring(3)
        }

        when (key) {
            "T" -> if (index == 0) {
                loadT(value)
            }

            "N" ->
                // Les lignes de type N seront parsées à la fin. preloadN() se chargera d'abord de réarranger la ligne
                // pour bien gérer les parenthèses.
                unparsedNote.add(index.toString() + value)

            "M" ->
                loadM(value)

            "L" ->
                loadL(index, value)

            "Y" ->
                loadY(index, value)

            "E" ->
                loadE(index, value)

            "U" ->
                loadU(value)

            "O" ->
                loadO(index, value)

            "I" ->
                loadI(value, regexIgnoreString)
        }

    }

    suspend fun loadI(line: String, regexIgnoreString: String = "") {
        val lineSplit = line.split(":")
        val lastSlashInFilename = currentFile.lastIndexOf('/')
        val fileNamePrefix = if (lastSlashInFilename != -1) {
            currentFile.substring(0, lastSlashInFilename + 1)
        } else {
            ""
        }
        val regexIgnoreString = try {
            if (regexIgnoreString != "") {
                regexIgnoreString + "|" + lineSplit[1]
            } else {
                lineSplit[1]
            }
        } catch (e: Exception) {
            "^:"
        }
        val filePath = fileNamePrefix + lineSplit[0]
        val lines = try {
            fileRepository.readFileLines(filePath)
        } catch (e: Exception) {
            println("Opening $filePath raised exception {${e.message}")
            emptyList()
        }
        try {
            val ignoredLines = Regex(regexIgnoreString)
            lines.forEach {
                val match = ignoredLines.find(it)
                if (match == null) {
                    parseOneLine(it, regexIgnoreString)
                }
            }
        } catch (e: Exception) {

        }
    }

    private fun loadM(value: String) {
        val metaChunks: List<String> = value.split(REGEX_PARSE_META)
        metaChunks.forEach { parseMeta(it) }
        parseMeta(value)
        sharedScreenModel.setMeasure(meta["m"] ?: "")
        sharedScreenModel.setSongTitle(meta["t"] ?: "")
        sharedScreenModel.setSongAuthor(meta["a"] ?: "")
        sharedScreenModel.setSongComposer(meta["h"] ?: "")
        sharedScreenModel.setSongRhythm(meta["r"] ?: "")
    }

    private fun loadN(voiceNumber: Int, line: String, templateComments: MutableList<String>) {
        var midiDuration = 0
        var nextDuration = 0
        var lastNoteString = ""
        val midiPitch = MidiPitch(voiceNumber)
        var nextComment: MutableList<String> = mutableListOf()
        var trioletCount = -1
        var nextTrioletDuration = 0
        fun pushMidi(typeBlock: String = "note") {
            if (typeBlock == "measure") {
                val z = midiPitch.copy()
                if (z.tick != 0) {
                    val tickMeta = midiPitch.tick
                    val numerator = midiPitch.metaBytes.toByteArray()[0].toInt()
                    val denominatorPower = midiPitch.metaBytes.toByteArray()[1].toInt()
                    val denominator = 1 shl denominatorPower
                    z.tick = 0
                    val newNumerator = tickMeta / 15 / denominator
                    z.metaBytes = "" + newNumerator.toChar() + denominatorPower.toChar() + 60.toChar() + 20.toChar()

                    pitches.add(z)
                }
                return;
            }
            if (midiDuration > 0 && typeBlock == "note") {
                midiPitch.setNote(lastNoteString, midiDuration)
                pitches.add(midiPitch.copy())
                midiPitch.setMarker(nextComment)
            } else if (typeBlock == "meta") {
                pitches.add(midiPitch.copy())
            }
            midiPitch.metaBytes = ""
            midiPitch.metaType = -1
            midiPitch.duration = 0
            lastNoteString = ""
            midiDuration = 0
            nextComment = mutableListOf()
        }

        midiPitch.currentVoiceNumber(voiceNumber)
        if (voiceNumber == 1) {
            midiPitch.initKey(meta["C"] ?: "C")
            pushMidi("meta")
            if (midiPitch.initMeasure(meta["m"] ?: "4/4")) {
                pushMidi("measure")
                pushMidi("meta")
            }
        }
        var lastIt = ' '
        val newN = POneVoiceNote()
        val lineRepeated = REGEX_REPETITION.replace(line) { matchResult ->
            val (charIterated, iteration) = matchResult.destructured
            val nTimes = iteration.toInt()
            charIterated.repeat(nTimes)
        }
        var commentNumber = -1
        val lineChar = lineRepeated.toCharArray()
        lineChar.forEach {
            when (it) {
                '|', ':', '!', '/' -> {
                    if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') {
                    } else {
                        midiDuration += nextDuration
                        nextDuration = 60
                    }
                }

                '.' -> {
                    if (lastIt == ';') {
                        pushMidi()
                        lastNoteString = "z"
                        midiDuration = 15
                        nextDuration = 30
                    }
                    midiDuration += nextDuration - 30
                    nextDuration = 30
                }

                ';' -> {
                    if (lastIt == '.') {
                        pushMidi()
                        lastNoteString = "z"
                        midiDuration = 15
                        nextDuration = 15
                    }
                    if (nextDuration >= 45) {
                        midiDuration += nextDuration - 45
                        nextDuration = 45
                    } else {
                        midiDuration += nextDuration - 15
                        nextDuration = 15
                    }
                }

                '\'', ',' -> {
                    newN.appendLastNote(it)
                    lastNoteString += it
                }

                '(', '[' -> newN.addNextMarker(it)
                ')', ']' -> newN.appendLastMarker(it)
                'd', 'r', 'm', 'f', 's', 'l', 't',
                'D', 'R', 'F', 'S', 'T',
                '-', 'Z', 'z'
                    -> {
                    if (trioletCount == 2) {
                        trioletCount = 1
                        midiDuration = nextTrioletDuration / 2
                        nextTrioletDuration = nextTrioletDuration - midiDuration
                        pushMidi()
                        nextDuration = nextTrioletDuration
                    } else if (trioletCount == 1) {
                        midiDuration = nextDuration
                        pushMidi()
                        trioletCount = 0
                        nextTrioletDuration = 0
                    }
                    var noteString = it.toString().lowercase()
                    if (it == 'D' || it == 'R' || it == 'F' || it == 'S') {
                        noteString += "i"
                    }
                    if (it == 'T') {
                        noteString += "a"
                    }
                    newN.addItem(noteString)
                    when (noteString) {
                        "-" -> {
                            midiDuration += nextDuration
                            nextDuration = 0
                        }

                        "z", "@" -> {
                            if (lastNoteString == "z") {
                                midiDuration += nextDuration
                                nextDuration = 0
                            } else {
                                pushMidi()
                                //noteString =
                            }
                            lastNoteString = "z"
                        }

                        "'", "(", ")", "[", "]", "," -> {}
                        else -> {
                            pushMidi()
                            lastNoteString = noteString
                        }
                    }
                }

                '@' -> {
                    if (lastNoteString == "z") {
                        midiDuration += nextDuration
                        nextDuration = 0
                    } else {
                        pushMidi()
                    }
                    lastNoteString = "z"
                }

                'i', 'a' ->
                    lastNoteString += it

                '&' -> {
                    commentNumber++
                    nextComment.add(templateComments[commentNumber])
                    if (templateComments[commentNumber] == "\$T") {
                        nextTrioletDuration = 2 * nextDuration / 3
                        trioletCount = 2
                        nextDuration = nextDuration - nextTrioletDuration                    }
                }
            }
            lastIt = it
        }
        pushMidi()
        /*if (voiceNumber == 1) {
            N.add(0, POneVoiceNote())
        }*/
        N.addWithPadding(voiceNumber, newN)
        midiPitch.setNote("z", 0)
        pitches.add(midiPitch.copy())
    }

    private fun loadT(line: String) {
        templateString = line
        var nextTemplate = ""
        val nextMarker: MutableList<PMarkers> = mutableListOf()
        var tMarker = ""
        val lineChar = line.toCharArray()
        var offset: Int = 0
        var templateOffset = 0
        lineChar.forEach {
            if (tMarker != "") {
                tMarker += it
                if ((it == '}')
                    || (tMarker.length == 2 && it != '{')
                ) {
                    TimeUnitObject.hasMarker(true)
                    nextMarker.add(PMarkers(tMarker, offset - templateOffset))
                    tMarker = ""
                }
                return@forEach
            }
            if (it == '$') {
                tMarker = "$"
                return@forEach
            }
            offset++
            if (it == ':' || it == '|' || it == '/' || it == '!') {
                val newTemplateItem =
                    PTemplate(nextTemplate, it.toString(), nextMarker.toMutableList())
                T.add(newTemplateItem)
                nextMarker.clear()
                nextTemplate = ""
                templateOffset = offset
            } else {
                nextTemplate += it
            }
        }
        val newTemplateItem = PTemplate(nextTemplate, "}", nextMarker)
        T.add(newTemplateItem)
    }

    private fun decodeDuration(duration: String): Int {
        return when (duration) {
            "0" -> 0
            "1" -> 1
            "2" -> 2
            "3" -> 3
            "4" -> 4
            "6" -> 6
            "8" -> 8
            "A" -> 10
            "B" -> 11
            "C" -> 12
            "E" -> 14
            "G" -> 16
            "K" -> 20
            "O" -> 24
            "S" -> 28
            "W" -> 32
            else -> 0
        }
    }
    fun loadU(line: String) {
        val measureLong = meta.getOrElse("m") { "4/4" }
        val measure = measureLong.split("/")[0].toIntOrNull() ?: 4
        val uObject = ParseULine(line, measure)
        val parsedULine = uObject.parsed()
        loadT(parsedULine)

        val regex = Regex("""z([^:]):""")
        val match = regex.find(line)
        val silentDuration = match?.groupValues?.get(1) ?: "0"

        sharedScreenModel.setMeasureInt(measure)
        sharedScreenModel.setSilentDurBefr(decodeDuration(silentDuration))
//        println(silentDuration)
    }

    fun loadO(index: Int, line: String) {
        O.getOrPut(index) { mutableListOf() }.add(line)
    }

    fun parseMeta(line: String) {
        /* $_a_keyAbbrev = array(
  'a' => 'author',
  'c' => 'tonality',
  'h' => 'composer',
  'i' => 'interline',
  'l' => 'lyrics font size',
  'm' => 'measure',
  'n' => 'note font size',
  'r' => 'rhythm',
  't' => 'title',
);*/
        if (line.trim() == "") {
            return
        }
        val subKey: String = line.substring(0, 1).trim()
        if (subKey == "|") {
            return
        }
        if (subKey != "" && "M" != subKey) {
            meta[subKey] = line.substring(2)
        }
        if ("c" == subKey) {
            val tonality = line.uppercaseFirstMeta()
            meta["C"] = tonality
            sharedScreenModel.setSongKey(tonality)
            sharedScreenModel.setTransposeAsIf(tonality)
        }
    }

    private fun String.uppercaseFirstMeta(): String {
        if (this.length < 2) {
            return ""
        }
        return get(2).uppercaseChar() + substring(3)
    }

    private fun String.addHyphens(): String {
        return this
            .replace(Regex("(?<![\\?:,\\.; ])_"), "-_")
            .replace(Regex("_-(?=_)"), "_")
            .replace(" -_", "_")
            .replace("--_", "-_")
            .replace(Regex("_$"), "")
    }

    private fun unpackLyrics(lyrics: String): String {
        val comments = REGEX_COMMENT.findAll(lyrics)
        val commentsIterator = comments.iterator()
        val loadedLyrics = lyrics//.replace(REGEX_LYRICS_COMMENT, "")
            .replace(REGEX_LYRICS_REPETITION) { matchResult ->
                val repeating = matchResult.destructured.match.groupValues[1]
                "_".repeat(repeating.toString().toInt())
            }
            .addHyphens()
        val lyricsFinal = REGEX_COMMENT.replace(loadedLyrics) { matchResult ->
            commentsIterator.next().value
        }
        return lyricsFinal
    }

    private fun loadL(stanzaNumber: Int, lyrics: String) {
        val unpackedLyrics = unpackLyrics(lyrics)
        loadLyrics(stanzaNumber, unpackedLyrics)
    }

    fun setOverrideLyrics(stanzaNumber: Int, i: Int, voice: Int, text: String) {
        while (L.size <= i) {
            L.add(POneStanzaLyrics())
        }
        L[i].setAlternativeLyrics(stanzaNumber, voice, text)
    }

    private fun loadLyrics(stanzaNumber: Int, lyrics: String) {
        defineRefrainMaxStanza(99999)
        val overrideIterator: MutableMap<Int, Iterator<String>> = mutableMapOf()
        try {
            val lyrics = getLyricsComments(lyrics)
            val positionDS = ArrayDeque<Int>()
            val arrayLyrics = lyrics.split(Regex("[/_]"))
            arrayLyrics.forEachIndexed { i, lyricsItem ->
                if (lyricsItem.contains($$"${x:}")) {
                    positionDS.add(i)
                }
                val originalLyrics = REGEX_STRIP_DC.replace(lyricsItem, "")
                val REGEX_OVERRIDE = Regex(".*\\$\\{O:(\\d+)\\}.*")
                if (REGEX_OVERRIDE.matches(lyricsItem)) {
                    val overrideNumber = lyricsItem.replace(REGEX_OVERRIDE, "$1").toInt()
                    val override = O.getOrElse(overrideNumber) { null }
                    override?.forEachIndexed { index, value ->
                        val matchResult = Regex("v(\\d+):(.*)").find(value)
                        // 1. Extraire le numéro de voix (ex: "2")
                        val voiceNum = matchResult?.groupValues?.get(1) ?: ""
                        val prefix = if (voiceNum.isNotEmpty()) {
                            // .map transforme chaque caractère '3', '4' en "3.", "4."
                            // .joinToString("") les colle ensemble -> "3.4."
                            voiceNum.map { "$it." }.joinToString("")
                        } else {
                            ""
                        }
                        val overrideString = when (smartLyricsType) {
                            "E" -> smartELyrics(matchResult?.groupValues[2] ?: value)
                            "Y" -> smartYLyrics(matchResult?.groupValues[2] ?: value)
                            else -> value
                        }
                        for (iN in (1..9)) {
                            if (matchResult?.groupValues[1]?.contains(iN.digitToChar()) ?: false) {
                                val overrideSyllabus = overrideString.split(Regex("[_/]"))
                                    .map { syllable ->
                                        if (syllable.isNotBlank()) "$prefix$syllable" else syllable
                                    }
                                    .iterator()
                                overrideIterator[iN] = overrideSyllabus
                            }
                        }
                    }
                }
                if (overrideIterator.isNotEmpty()) {
                    overrideIterator.map { (voice, textIterator) ->
                        if (textIterator.hasNext()) {
                            setOverrideLyrics(stanzaNumber, i, voice, textIterator.next())
                        }
                    }
                }
                addLyricsItem(stanzaNumber, i, originalLyrics)
            }
            if (refrainBeginsAt >= 0 && stanzaNumber > 1) {
                copyRefrainToStanza(stanzaNumber)
            }

            if (lyricsComment.isNotEmpty()) {
                val lyricsIterator = lyricsComment.iterator()
                while (lyricsIterator.hasNext()) {
                    var item = lyricsIterator.next()
                    // @todo: if item == ${O:...}
                    // ${D:xxx} lyrics of DC and DSs
                    val isDS = Regex("\\$\\{D([^:]*):").find(item)
                    if (isDS != null) {
                        item = item.replace(REGEX_LYRICS_COMMENT, "$2")
                        val signDS = isDS.groupValues[1]
                        val item = when (smartLyricsType) {
                            "E" -> smartELyrics(item.replace(" _", " "))
                            "Y" -> smartYLyrics(item)
                            else -> item
                        }
                        var pDS = positionDS.removeFirstOrNull() ?: 0
                        if (signDS != "S") {
                            pDS = 0
                        }
                        item
                            .addHyphens()
                            .split(Regex("[_/]"))
                            .forEachIndexed { i, xval ->
                                appendLyricsItem(stanzaNumber, i + pDS, xval)
                            }
                        lyricsIterator.remove()
                    }
                }
            }
        } catch (e: Exception) {
            sharedScreenModel.appendData("\nErr: $e")
        }
        if (stanzaNumber > sharedScreenModel.nbStanzas.value) {
            sharedScreenModel.setNbStanzas(stanzaNumber)
        }
        lyricsComment.clear()
    }

    fun smartYLyrics(lyrics: String): String {
        // Les ${O:1} risquent de changer en ${O:_1}. Sauvegardons-les dans comments.
        val comments = REGEX_COMMENT.findAll(lyrics)
        val commentsIterator = comments.iterator()
        val loadedLyrics = lyrics
            .replace(REGEX_VOWELS_STAGE1, "$0_")
            .replace(REGEX_VOWELS_STAGE2, "$1_")
            .replace(REGEX_VOWELS_STAGE3, "$1_")
            .replace(" ", " _")
            .replace("_\\ _", " ")
            .replace("_\\", "")
            .replace("_0", "")
            .replace(REGEX_MALAGASY_MN, "$1$2_$3")
            .replace(REGEX_MALAGASY_MN_STAGE2, "$1-_")
            .replace("_n'", "n'_")
        // Et remettons les valeurs originales de ${...}
        val lyricsFinal = REGEX_COMMENT.replace(loadedLyrics) { matchResult ->
            commentsIterator.next().value
        }
        return unpackLyrics(lyricsFinal)
    }

    // loadY is a smart lyrics parser for Malagasy language
    private fun loadY(intKey: Int, lyrics: String) {
        smartLyricsType = "Y"
        val smartLyrics = smartYLyrics(lyrics)
        loadL(intKey, smartLyrics)
    }

    fun smartELyrics(lyrics: String): String {
        val loadedLyrics = lyrics
            .replace(" ", " _")
            .replace("\\ _", " ")
            .replace("_\\", "")
            .replace("_/", "/")
            .replace("_0", "")
        //getLyricsComments(loadedLyrics)
        return unpackLyrics(loadedLyrics)
    }

    // loadE is a smart Lyrics parser for English language
    private fun loadE(intKey: Int, lyrics: String) {
        smartLyricsType = "E"
        val smartLyrics = smartELyrics(lyrics)
        loadL(intKey, smartLyrics)
    }

    private fun getLyricsComments(lyrics: String): String {
        val matchResult = REGEX_LYRICS_COMMENT.findAll(lyrics)
            .map { it.value }.toList()
        if (matchResult.isNotEmpty()) {
            lyricsComment.addAll(matchResult)
        }
        return REGEX_STRIP_DC.replace(lyrics, $$"\\${x:}")
    }

    private fun defineRefrainFrom(tuoNumber: Int) {
        refrainBeginsAt = tuoNumber
        refrainEndsAt = 99999
        refrainMaxStanza = 99999
    }
    private fun defineRefrainTo(tuoNumber: Int) {
        refrainEndsAt = tuoNumber
    }
    private fun defineRefrainMaxStanza(tuoNumber: Int) {
        refrainMaxStanza = tuoNumber
    }

    private fun copyRefrainToStanza(stanzaNumber: Int) {
        val refrainMax = min(min(refrainEndsAt - 1, L.size - 1), refrainMaxStanza - 1)
        for (i in refrainBeginsAt..refrainMax) {
            L[i].setLyrics(stanzaNumber, L[i].getLyrics(1))
            L[i].copyAlternativeLyrics(1, stanzaNumber)
        }
    }

    private fun addLyricsItem(stanzaNumber: Int, i: Int, lyricsItem: String) {
        while (L.size <= i) {
            L.add(POneStanzaLyrics())
        }
        val strippedLyrics =
            if (lyricsItem.contains($$"${R=}")) {
                if (stanzaNumber == 1) {
                    defineRefrainFrom(i)
                }
                lyricsItem.replace($$"${R=}", "")
            } else if (lyricsItem.contains($$"${R!}")) {
                if (stanzaNumber == 1) {
                    defineRefrainTo(i)
                } else {
                    defineRefrainMaxStanza(i)
                }
                lyricsItem.replace($$"${R!}", "")
            } else {
                lyricsItem
            }
        L[i].setLyrics(stanzaNumber, strippedLyrics)
    }

    private fun appendLyricsItem(stanzaNumber: Int, i: Int, lyricsItem: String) {
        if (L.size > i) {
            L[i].appendDSLyrics(stanzaNumber, lyricsItem)
        }
    }

    fun MutableList<POneVoiceNote>.addWithPadding(index: Int, element: POneVoiceNote) {
        if (index < this.size) {
            // L'index existe déjà, on remplace l'élément.
            this[index] = element
        } else {
            // L'index n'existe pas encore.
            // On remplit d'abord les "trous" avec des notes par défaut.
            for (i in this.size until index) {
                this.add(POneVoiceNote())
            }
            // Puis on ajoute le nouvel élément à la fin de la liste,
            // ce qui est maintenant l'index cible.
            this.add(element)
        }
    }

    fun getNotePointerWithDebug(templatArray: List<String>, targetIdx: Int, sourceLine: String): Int {
        println("\n=== DÉBOGAGE ALIGNEMENT RYTHME / NOTES (CORRIGÉ) ===")

        val bodyOnly = sourceLine.substringAfter(":")
        val expandedBody = expandNotes(bodyOnly)
        // On suppose que parseNoteLineToArray découpe chaque "événement" (note ou prolongement)
        val notesList = parseNoteLineToArray("N:$expandedBody")

        println("\n=== ALIGNEMENT STRICT : TEMPLATE -> SOURCE ===")
        println("Ligne Source : ${sourceLine.substringBefore(":")}")
        println("----------------------------------------------------------------------")
        println("%-5s | %-12s | %-15s | %-10s".format("IDX", "BLOC TEMP.", "NOTES SRC", "POINTEUR"))
        println("----------------------------------------------------------------------")

        var currentPointer = 0
        var notesInTargetBlock = mutableListOf<String>()
        var countInTarget = 0

        for (i in templatArray.indices) {
            val block = templatArray[i]

            // NETTOYAGE : On ignore les marqueurs $Q, ${DC} pour le comptage
            val markerRegex = Regex("""\$\{.*?\}|\$\w+""")
            val cleanBlock = block.replace(markerRegex, "")

            // RÈGLE : On compte uniquement les LETTRES (S, F, D, R, M, L, T, d, r...)
            val nbLettres = cleanBlock.count { it.isLetter() && it.lowercaseChar() != 'z' }

            // On récupère les notes correspondantes dans le fichier source
            val blocNotes = (0 until nbLettres).map { offset ->
                notesList.getOrNull(currentPointer + offset) ?: "[VIDE]"
            }

            if (i == targetIdx) {
                countInTarget = nbLettres
                notesInTargetBlock.addAll(blocNotes)
            }

            val marker = if (i == targetIdx) " [ CIBLE ]" else ""
            println("[%3d] | %-12s | %-15s | Index: %d %s"
                .format(i, block, blocNotes.joinToString(","), currentPointer, marker))

            // On incrémente le pointeur pour le prochain bloc
            // SEULEMENT si nous n'avons pas encore dépassé la cible
//            if (i < targetIdx) {
            currentPointer += nbLettres
//            }
        }

        println("----------------------------------------------------------------------")
        println("RÉSULTAT : Le bloc $targetIdx contient $countInTarget lettre(s).")
        println("Notes pointées dans le fichier source : ${notesInTargetBlock.joinToString(" ET ")}")
        println("Pointeur de départ (offset) : $currentPointer")
        println("======================================================================\n")

        return currentPointer
    }

    fun getNotePointer(templatArray: List<String>, targetIdx: Int, sourceLine: String): Int {
        val bodyOnly = sourceLine.substringAfter(":")
        val expandedBody = expandNotes(bodyOnly)
        val notesList = parseNoteLineToArray("N:$expandedBody")
        var currentPointer = 0
        var notesInTargetBlock = mutableListOf<String>()
        var countInTarget = 0

        for (i in templatArray.indices) {
            val block = templatArray[i]

            val markerRegex = Regex("""\$\{.*?\}|\$\w+""")
            val cleanBlock = block.replace(markerRegex, "")

            val nbLettres = cleanBlock.count { it.isLetter() && it.lowercaseChar() != 'z' }

            val blocNotes = (0 until nbLettres).map { offset ->
                notesList.getOrNull(currentPointer + offset) ?: "[VIDE]"
            }

            if (i == targetIdx) {
                countInTarget = nbLettres
                notesInTargetBlock.addAll(blocNotes)
            }

            val marker = if (i == targetIdx) " [ CIBLE ]" else ""

            if (i < targetIdx) {
                currentPointer += nbLettres
            }
        }
        return currentPointer
    }
}
