package mg.dot.feufaro.solfa

import SharedScreenModel
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import feufaro.composeapp.generated.resources.Emmentaler
import feufaro.composeapp.generated.resources.PTSerif_Bold
import feufaro.composeapp.generated.resources.PT_Serif_Bold_Italic
import feufaro.composeapp.generated.resources.Res
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mg.dot.feufaro.ContextualMenu
import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.TUODetailDialog
import mg.dot.feufaro.data.GridTUOData
import mg.dot.feufaro.getPathValidator
import mg.dot.feufaro.ui.rememberFileSaveLauncher
import mg.dot.feufaro.viewmodel.MidiMarkers
import mg.dot.feufaro.viewmodel.SolfaScreenModel
import org.jetbrains.compose.resources.Font
import java.io.File
import kotlin.math.min
import kotlin.math.roundToInt

val FEUFAROO_TRIOLET_COLOR = Color.DarkGray
val FEUFAROO_KEY_CHANGE_COLOR = Color.Blue
@Stable
class TimeUnitObject (val pTemplate: PTemplate, val prevTUO: TimeUnitObject?, countOfN: Int) {
    var mutableNoteVersion: Int by mutableStateOf(0)
    private var lyrics: MutableList<POneStanzaLyrics> = mutableListOf()
    var sep0: String = ""
    val tuNotes: MutableList<TUNote> = mutableListOf()
    val numBlock: Int
    var markerBefore: String = ""
    private var annotated : Boolean
    var transposeNewKey: String = ""
    var transposeOldKey: String = ""
    var firstTuoIndex: Int = -1
    companion object {
        var nbBlock: Int = 0
        var sep1: String = ""
        var _hasMarker: Boolean by mutableStateOf(false)
        var nbStanzas: Int = 0
        fun hasMarker(yes: Boolean) {
            _hasMarker = yes
        }
        var lastHairPinStart: Int = -1
        var lastHairPinSymbol: Char? = null
        fun startHairPin(hairPinSymbol: Char, hairPinBlock: Int) {
            lastHairPinSymbol = hairPinSymbol
            lastHairPinStart = hairPinBlock
        }
        fun endHairPin() {
            lastHairPinStart = -1
            lastHairPinSymbol = null
        }
        var transposeAsIf: String = ""
        var transposeTo: String = ""
        fun changeTransposeAsIf(asIf: String) {
            transposeAsIf = asIf
        }
        fun changeTransposeTo(to: String) {
            transposeTo = to
        }
    }
    init {
        nbBlock++
        numBlock = nbBlock
        sep0 = sep1
        sep1 = pTemplate.separatorAfter
        nbStanzas = 0
        annotated = false
        if (pTemplate.template == "") {
            repeat(countOfN) {
                tuNotes.add(it, TUNote())
            }
        }
    }
    fun hasHairPin(): Char? {
        if (pTemplate.hasMarker($$"${<}")) {
            return '<'
        }
        if (pTemplate.hasMarker($$"${>}")) {
            return '>'
        }
        if (pTemplate.hasMarker($$"${=}")) {
            return '='
        }
        return null
    }
    fun isTriolet(): Boolean {
        return pTemplate.hasMarker($$"$T")
    }
    fun addLyrics(lyricsIn: POneStanzaLyrics) {
        lyrics.add(lyricsIn)
    }
    private fun addNote(voiceNumber: Int, noteString: String, nextMarker: Char) {
        val tuNote = tuNotes.getOrNull(voiceNumber)?.addNote(noteString)
        if (tuNote == null) {
            val prevTUNote: TUNote? = prevTUO?.tuNotes?.getOrNull(voiceNumber)
            val newNote = TUNote()
            newNote.prevTUNote = prevTUNote
            newNote.prevTUO = prevTUO
            newNote.addNote(noteString)
            newNote.addMarker('0', markerBefore)
            tuNotes.add(voiceNumber, newNote)
        }
        tuNotes[voiceNumber].addMarker(nextMarker)
    }
    fun addNote(voiceNumber: Int, oneVoiceNote: MutableList<PNotes>, nIndex: Int, nextMarker: Char) {
        val thisNote: Note? = oneVoiceNote.getOrNull(nIndex)?.toNote()
        val tuNote = tuNotes.getOrNull(voiceNumber)
        if (tuNote == null) {
            val newTUNote = newTUNote(voiceNumber, thisNote, nextMarker, markerBefore)
            tuNotes.add(voiceNumber, newTUNote)
        } else {
            tuNote.from(thisNote)
        }
    }
    private fun newTUNote(voiceNumber: Int, thisNote: Note?, newMarker: Char, markerBefore: String): TUNote {
        val prevTUNote: TUNote? = prevTUO?.tuNotes?.getOrNull(voiceNumber)
        val newNote = TUNote()
        newNote.prevTUNote = prevTUNote
        newNote.prevTUO = prevTUO
        newNote.from(thisNote)
        if (newMarker != ' ') {
            newNote.addMarker(newMarker, markerBefore)
        }
        return newNote
    }
    fun markBefore(newMarker: Char) {
        markerBefore += newMarker
    }
    fun addMarker(newMarker: Char) {
        if (tuNotes.isEmpty()) {
            markBefore(newMarker)
        }
        tuNotes.forEach {
            it.addMarker(newMarker)
        }
    }
    fun addBlank(voiceNumber: Int, blank: Char) {
        val blankString = if (blank == '-' || blank == 'w') "-" else ""
        addNote(voiceNumber, blankString, '0')
    }
    fun getNum(): Int {
        return numBlock
    }
    fun setOldKey(theOldKey: String) {
        transposeOldKey = theOldKey
    }
    fun noteAsMultiString(): String {
        val separatorBefore = when (sep0) {
            ":" -> sep0
            "!" -> "|"
            else -> ""
        }
        val markersBefore = ""
        val transposeFrom = pTemplate.hasKeyChange()
        val transposeTo = if (transposeFrom != "") {
            transposeOldKey = transposeFrom
            prevTUO?.transposeOldKey ?: "Bb"
        } else {
            if (transposeOldKey != prevTUO?.transposeOldKey) {
                transposeOldKey = prevTUO?.transposeOldKey ?: transposeOldKey
            }
            ""
        }
        if (pTemplate.template == "z") {
            return "$separatorBefore\n$separatorBefore\n$separatorBefore\n$separatorBefore"
        }
        val retour = tuNotes.drop(1).joinToString (separator = "\n"){
            separatorBefore + markersBefore + it.toString(transposeFrom, transposeTo).replace(Regex("[\\(\\)\\[\\]]"), "")
        }
        return retour
    }
    fun annotate() {
        // We must annotate once, because, annotations can be called asynchronously after first filling
        if (!annotated) {
            tuNotes.forEachIndexed { voiceNumber, tuNote ->
                tuNote.annotate(voiceNumber)
            }
            annotated = true
        }
    }
    fun annotations(): List<AnnotatedTUO> {
        val xreturn = tuNotes.mapNotNull {
            it.annotations
        }
        return xreturn
    }

    fun lyricsAsMultiString(stanzaNumber: Int): String {
        var result: MutableList<String> = emptyList<String>().toMutableList()
        lyrics.forEach {
            val byLines = it.toList(stanzaNumber)
            byLines.forEachIndexed { i, v ->
                while(result.size <= i) {
                    result.add(i, "")
                }
                result[i] += "\u00A0$v"
            }
        }
        return result.joinToString(separator = "\n") { it }
    }
            fun getSingleSyllable(stanzaNumber: Int): List<String> {
                val finalLines = mutableListOf<String>()

                lyrics.forEach { stanzaLyrics ->
                    val lines = stanzaLyrics.toList(stanzaNumber)
                    lines.forEachIndexed { index, text ->
                        val trimmed = text.trim()
                        if (index < finalLines.size) {
                            finalLines[index] = "${finalLines[index]} $trimmed".trim()
                        } else {
                            finalLines.add(trimmed)
                        }
                    }
                }

                return finalLines
            }
}

@Composable
fun TimeUnitComposable(
    tuo: TimeUnitObject,
    stanzaNumber: Int,
    gridColumnCount: Int,
    gridActive: Boolean,
    isCurrentBlock: Boolean = false,
    parentFocusRequester: FocusRequester? = null,
    transpositionInterval: Int
) {
    val col = if (tuo.getNum() % 2 == 0) MaterialTheme.colorScheme.tertiary.copy(alpha = 0.05f) else MaterialTheme.colorScheme.error.copy(alpha = 0.05f)
    val currentDensity = LocalDensity.current

    val animatedColor by animateColorAsState(
        targetValue = if (gridActive) MaterialTheme.colorScheme.secondary.copy(alpha = 1f) else col,
        animationSpec = tween(durationMillis = 100) // Très court pour rester réactif
    )
    val focusRequesters = remember { List(4) { FocusRequester() } }

    Column(
        modifier = Modifier
            .background(animatedColor)
    ) {
        if (TimeUnitObject._hasMarker) {
            val lineHeight = 20.sp
            val density = LocalDensity.current
            val lineHeightDp : Dp = with(density) {
                lineHeight.toDp()
            }
            var fontStyle = FontStyle.Normal
            var fontWeight = FontWeight.Normal
            val hairPinSymbol = tuo.hasHairPin()
            val yHeight = with(density) { lineHeightDp.toPx()}

            /*if (tuo.isTriolet()) {
                Canvas(modifier = Modifier.fillMaxSize()) {
                    val arcWidth = with(density) { size.width * 0.75f}
                    drawArc(
                        color = FEUFAROO_TRIOLET_COLOR,
                        startAngle = 200f,
                        sweepAngle = 140f,
                        useCenter = false,
                        topLeft = Offset(0f, yHeight / 2),
                        size = Size(arcWidth, yHeight),
                        style = Stroke(width = 2f)

                    )
                }
            }
            if ((hairPinSymbol == '=') && (TimeUnitObject.lastHairPinSymbol != null)) {
                println("LastHairpin: ${TimeUnitObject.lastHairPinSymbol} ${TimeUnitObject.lastHairPinStart}")
                val hairPinStart = TimeUnitObject.lastHairPinStart
                val lastHairPinSymbol = TimeUnitObject.lastHairPinSymbol
                val hairPinStartLine: Int  = (hairPinStart - 1) / gridColumnCount
                val hairPinEndLine: Int = (tuo.numBlock - 1) / gridColumnCount
                if (hairPinStartLine == hairPinEndLine) {
                    Canvas(
                        modifier = Modifier.fillMaxSize()
                    ) {
                        val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width/2
                        val xEnd = if (lastHairPinSymbol == '>') size.width/2 else -size.width * (tuo.numBlock - hairPinStart)
                    drawLine(
                        Color.DarkGray,
                        start = Offset(x=xStart, y=0f),
                        end = Offset(xEnd, yHeight/2)
                    )
                        drawLine(
                            Color.DarkGray,
                            start = Offset(xStart, yHeight),
                            end = Offset(xEnd, yHeight/2)
                        )
                    }
                    TimeUnitObject.endHairPin()
                }

            }
            if (hairPinSymbol != null && hairPinSymbol != '=') {
                TimeUnitObject.startHairPin(hairPinSymbol, tuo.numBlock)
            }
            // @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
            // problème si template = $QD:,-$QD
            tuo.pTemplate.resetCalledMarker()
            val text = tuo.pTemplate.markerToString()
            if (text.contains(Regex("^([mfp]+|[rR]it.*)$"))) {
                fontStyle = FontStyle.Italic
            }
            if (text.contains("p")) {
                fontWeight = FontWeight.Bold
            }*/
            /*AutoResizingText(
                text = text,
                minFontSize = 18.sp,
                maxFontSize = 18.sp,
                maxLines = 2,
                fontStyle = fontStyle,
                fontWeight = fontWeight,
                modifier = Modifier.fillMaxWidth()
                    .height(lineHeightDp))*/


        }
        Row (modifier = Modifier.height(IntrinsicSize.Min)){
            if (tuo.sep0 == "/" || tuo.sep0 == "|") {
                Box(
                    modifier = Modifier
                        .width(1.dp)
                        .fillMaxHeight()
                        .background(Color.Black)
                )
                if (tuo.sep0 == "/") {
                    Box(modifier = Modifier.width(2.dp))
                    Box(
                        modifier = Modifier
                            .width(1.dp)
                            .fillMaxHeight()
                            .background(Color.Black)
                    )
                }
                Box(
                    modifier = Modifier
                        .width(4.dp)
                        .fillMaxHeight()
                )
            }
            tuo.annotate()
            // Utile pour que Compose recalcule la cellule. La valeur sera toutefois toujours égale à ""
            val mutableNoteVersionX = if (tuo.mutableNoteVersion == -1) "7" else ""
            val multiLineNotes = tuo.noteAsMultiString()
            val multiLineText = Transpose.transposeText(text = multiLineNotes, interval = transpositionInterval)
            var textLayoutResult: TextLayoutResult? by remember { mutableStateOf(null) }
            Box (modifier = Modifier.fillMaxWidth()
                .drawBehind {
                    tuo.annotations().map { ta ->
                        ta.underlineSpec.map { us ->
                            var xStart = 0f
                            val separatorLength = if (tuo.sep0 in listOf(":", "!")) 1 else 0
                            val leftMarginUnderlineDp = when (tuo.sep0) {
                                "!" -> 8.dp
                                ":" -> 3.dp
                                else -> 0.dp
                            }
                            val leftMarginUnderline = with(currentDensity) {
                                leftMarginUnderlineDp.toPx()
                            }
                            if (us.x > -1) {
                                val lineGlobalStartOffset = textLayoutResult?.getLineStart(ta.voiceNumber - 1) ?: 0
                                val globalStartIndex = lineGlobalStartOffset + us.x
                                xStart = textLayoutResult?.getCursorRect(globalStartIndex)?.left ?: 0f
                                if (us.isNewParen) {
                                    xStart += leftMarginUnderline
                                }
                            }
                            var xEnd = size.width
                            if (us.y > -1 ) {
                                val lineGlobalStartOffset = textLayoutResult?.getLineStart(ta.voiceNumber - 1) ?: 0
                                // don't ask me why + 1 here makes it work, but it works.
                                val globalEndIndex = min(lineGlobalStartOffset + us.y + separatorLength + 1, multiLineText.length)

                                xEnd = textLayoutResult?.getCursorRect(globalEndIndex)?.right ?: size.width
                            }
                        //if (it.underlineSpec.size > 0) {
                            var colorUnderline = when (tuo.mutableNoteVersion) {
                                0 -> Color.DarkGray
                                1 -> Color.Yellow
                                2 -> Color.Magenta
                                3 -> Color.Blue
                                4 -> Color.Green
                                else -> Color.Black
                            }
                            val totalHeight = textLayoutResult?.size?.height ?: 0
                            val nbNotes = (multiLineText + mutableNoteVersionX).split("\n").size
                            val yPos = ta.voiceNumber * totalHeight.toFloat() / nbNotes
                            drawLine(
                                colorUnderline,
                                start = Offset(xStart, yPos),
                                end = Offset(xEnd, yPos)
                            )
                        }
                    }
                }
            ){
                val annotatedText = buildAnnotatedString {
                    multiLineText.split(">").mapIndexed { index, text ->
                        if (index %2 == 0) {
                            append(text)
                        } else {
                            withStyle(
                                style = SpanStyle(
                                    fontSize = 10.sp,
                                    baselineShift = BaselineShift.Superscript,
                                    //fontStyle = FontStyle.Italic,
                                    color = MaterialTheme.colorScheme.primary
                                )) {
                                    append(text+" ")
                                }
                        }

                    }
                }
                Text(
                    text = annotatedText,
                    style = MaterialTheme.typography.bodyLarge,
                    onTextLayout = { result ->
                        textLayoutResult = result
                    }
                 )
            }
        }
        /*AutoResizingText(
            text = tuo.lyricsAsMultiString(stanzaNumber),
            minFontSize = 16.sp,
            maxFontSize = 16.sp,
            maxLines = 2,
            modifier = Modifier.fillMaxWidth()
        )*/
    }
}

data class TUOWidthMeasure(val width: Dp, val isReady: Boolean)

@Composable
fun bestTUOWidth(items: List<TimeUnitObject>): TUOWidthMeasure {
    val textMeasurer = rememberTextMeasurer()
    val density = LocalDensity.current

    var maxWidth by remember(items) { mutableStateOf(0.dp) }
    var isReady by remember(items) { mutableStateOf(false) }
    LaunchedEffect(items) {
        isReady = false
        maxWidth = 0.dp
        items.forEach {
                val textLayoutResult: TextLayoutResult = textMeasurer.measure(
                    text = it.noteAsMultiString(),
                    maxLines = 1
                )
            val textWidth = with(density) {
                textLayoutResult.size.width.toDp()
            }
            if (textWidth > maxWidth) {
                maxWidth = textWidth
            }
        }
        isReady = true
    }
    return TUOWidthMeasure(maxWidth + 13.dp, isReady)
}
@Composable
fun AutoResizingText(
    text: String,
    modifier: Modifier = Modifier,
    maxFontSize: TextUnit = 24.sp, // Taille de police maximale de référence
    minFontSize: TextUnit = 10.sp, // Taille de police minimale autorisée
    textStyle: TextStyle = LocalTextStyle.current,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    maxLines: Int = 1 // Pour s'assurer que le texte ne déborde jamais sur deux lignes
) {
    var fontSize by remember { mutableStateOf(maxFontSize) }
    val textMeasurer = rememberTextMeasurer() // Mesure le texte hors de la composition
    val density = LocalDensity.current

    BoxWithConstraints(modifier = modifier) {
        // La largeur disponible pour le texte
        val maxWidthPx = with(density) { maxWidth.toPx() }

        // Un Text() invisible ou non affiché pour mesurer le texte sans le dessiner directement
        // Vous pouvez aussi utiliser textMeasurer.measure() directement
        // On rend le texte visible une fois la taille ajustée pour éviter les clignotements.
        var readyToDraw by remember { mutableStateOf(false) }
        val textLinesCount = text.split("\n").size * maxLines

        Text(
            text = text,
            // Utiliser le même style que le Text final, mais ajuster la taille
            style = textStyle.copy(fontSize = fontSize),
            maxLines = textLinesCount, // Très important pour qu'il ne déborde pas en hauteur
            overflow = TextOverflow.Clip, // Masque si ça déborde encore (ne devrait pas si le calcul est bon)
            // onTextLayout est appelé après que le texte a été mesuré
            onTextLayout = { textLayoutResult: TextLayoutResult ->
                if (textLayoutResult.hasVisualOverflow && fontSize > minFontSize) {
                    // Si le texte déborde en largeur, réduisez la taille de la police
                    // Vous pouvez utiliser un pas plus fin ou une méthode de recherche binaire pour optimiser.
                    fontSize *= 0.9f // Réduire de 10% à chaque fois
                } else {
                    readyToDraw = true // La taille est stable ou min atteinte, on peut dessiner
                }
            },
            fontStyle  = fontStyle,
            fontWeight = fontWeight,
            // Le modificateur drawWithContent est utilisé pour retarder le dessin
            // jusqu'à ce que la taille de police finale soit déterminée.
            softWrap = false,
            modifier = Modifier.fillMaxWidth()
                .drawWithContent {
                    if (readyToDraw) {
                        drawContent()
                    }
                }
        )
    }
}
@Composable
fun LazyVerticalGridTUO(
    viewModel: GridTUOData,
    gridWidthPx: Int,
    sharedScreenModel: SharedScreenModel,
    onGridWidthMeasured: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
    val tuoList  = viewModel.tuoList
    key(tuoList) {

    var menuPosition by remember { mutableStateOf(Offset.Zero) }
    var showAddDialog by remember { mutableStateOf(false) }
    var showDetailDialog by remember { mutableStateOf(false) }
    var showEditDialog by remember { mutableStateOf(false) }
    var showContextualMenu by remember { mutableStateOf(false) }
    var selectedTUO by remember { mutableStateOf<TimeUnitObject?>(null) }
    var selectedIndex by remember { mutableStateOf(-1) }
    val regexMeasure = Regex("(\\d)/\\d").find(viewModel.measure)

    val focusRequester = remember { FocusRequester() }
    var selectedGridIndex by remember { mutableStateOf(0) }

    LaunchedEffect(tuoList) {
        showContextualMenu = false
        showEditDialog = false
        showDetailDialog = false
        showAddDialog = false
        selectedIndex = -1
        selectedGridIndex = -1
        selectedTUO = null
    }

    val editMode by sharedScreenModel.modeEditor.collectAsState()
    val showFullChord by sharedScreenModel.harmonyView.collectAsState()
    var toggleWithDegreeAndChord by remember { mutableStateOf (false) }

    val focusManager = LocalFocusManager.current
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .onSizeChanged { size ->
                onGridWidthMeasured(size.width)
            }
            .focusRequester(focusRequester)
            .focusable()
    ) {
        LaunchedEffect(editMode) {
            if (editMode) {
                focusRequester.requestFocus()
            }
        }
        val density = LocalDensity.current
        val horizontalArrangementSpacing: Dp = 0.dp
        val columnGroupString = regexMeasure?.groupValues?.get(1) ?: "1"
        val columnGroup = columnGroupString.toInt().coerceAtLeast(1)
        val tuoWidthMeasure = bestTUOWidth(tuoList)
        val itemMinBaseWidth = tuoWidthMeasure.width
        val gridWidthDp = with(density) { gridWidthPx.toDp() }

        // on affiche un loader pour éviter le "saut" visuel
        val isLayoutReady = tuoWidthMeasure.isReady && gridWidthDp > 0.dp

        val gridCount by sharedScreenModel.gridCount.collectAsState()

        var gridColumnCount: Int = remember(gridWidthDp, itemMinBaseWidth, columnGroup) {
            if (gridWidthDp == 0.dp) return@remember columnGroup
            var calculatedCols =
                (gridWidthDp / (itemMinBaseWidth + horizontalArrangementSpacing)).toInt()
            if (calculatedCols == 0) calculatedCols = columnGroup
            if (calculatedCols % columnGroup != 0) {
                calculatedCols = (calculatedCols / columnGroup) * columnGroup
                if (calculatedCols == 0) calculatedCols = columnGroup
            }
            val actualColumnCount: Int = calculatedCols.coerceAtLeast(columnGroup)
            actualColumnCount
        }
        gridColumnCount += gridCount
        val flowRowSize: Float = 0.98f / gridColumnCount

        val currentStanza = viewModel.stanza

        val tuoTimestamps by sharedScreenModel.tuoTimestamps.collectAsState()
        val activeRowIndex by sharedScreenModel.activeIndex.collectAsStateWithLifecycle()

        val measures = tuoList.drop(1).chunked(gridColumnCount)
        // Avant column affichage:
        val metadataList = remember(tuoList) {
            tuoList.drop(1).mapIndexedNotNull { globalIndex, oneTUO ->
                val rawMarker = oneTUO.pTemplate.markerToString()
                val hairPin = oneTUO.hasHairPin()
                val markerList = sharedScreenModel.parseMarkers(rawMarker)
                /*val finalMarker = when {
                    hairPin != null && markerText.isBlank()    -> hairPin.toString()
                    hairPin != null && markerText.isNotBlank() -> "${markerText.trim()}$hairPin"
                    markerText.isNotBlank()                    -> markerText
                    else                                       -> null
                }*/
                if (hairPin != null) {
                    markerList.add(hairPin.toString())
                }

                if (markerList.isNotEmpty()) {
                    val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
//                    println("MetaData[$globalIndex] marker='${markerList.joinToString(", ")}' hairpin=$hairPin")
                    MidiMarkers(
                        myTimestamp,
                        globalIndex,
                        oneTUO.pTemplate.template,
                        oneTUO.pTemplate.lastCalledMarker,
                        markerList,
                        oneTUO.prevTUO?.pTemplate?.template ?: "",
                        oneTUO.sep0,
                        oneTUO.tuNotes.getOrNull(1).toString()
                    )

                } else null
            }.distinctBy { it.gridIndex }
        }

    // Envoyer les données au ViewModel une seule fois
    LaunchedEffect(metadataList) {
        if (metadataList.isNotEmpty()) {
            println("Mise à jour MidiData avec ${metadataList.size}")
            sharedScreenModel.updateAndFinalizeMidiData(metadataList)
        }
    }
    LaunchedEffect(measures, sharedScreenModel.stanza.value) {
        sharedScreenModel.updateSyllablesFromList(
            measures = measures,
            stanzaNumber = sharedScreenModel.stanza.value
        )

        val totalSyllables = sharedScreenModel.synchronizedSyllables.value.size
        //println("Sync complète effectuée SUR stz: ${sharedScreenModel.stanza.value}! Total colonnes : $totalSyllables")
    }
    var emmentaler = FontFamily(Font(Res.font.Emmentaler))
    var ptSerifBoldItalic = FontFamily(Font(Res.font.PT_Serif_Bold_Italic))
    var ptSerif = FontFamily(Font(Res.font.PTSerif_Bold))

    val textMeasurer = rememberTextMeasurer()
    val containerWidthDp = gridWidthDp / gridColumnCount
    val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
    val isAnySyllableTooLong = remember(currentStanza, gridWidthDp, gridColumnCount) {
        val allMeasuresSyllables = tuoList.drop(1).chunked(gridColumnCount).map { measureTUOss ->
            measureTUOss.map { it.getSingleSyllable(currentStanza) }.map { column ->
                column.map { syllable -> syllable.replace(REGEX_CLEAN_PREFIX, "").trim() }
            }
        }

        allMeasuresSyllables.any { measure ->
            measure.any { column ->
                column.any { syl ->
                    val textLayoutResult = textMeasurer.measure(
                        text = syl,
                        maxLines = 1,
                        softWrap = false
                    )
                    val textWidthDp = with(density) { textLayoutResult.size.width.toDp() }
                    textWidthDp > containerWidthDp
                }
            }
        }
    }

    if (!isLayoutReady) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(200.dp),
            contentAlignment = Alignment.Center
        ) {
            CircularProgressIndicator(
                modifier = Modifier.size(64.dp),
                strokeWidth = 3.dp
            )
            Text(
                text = "𝄞 ♫",
                style = TextStyle(
                    fontSize = 28.sp,
                    color = MaterialTheme.colorScheme.primary
                )
            )
        }
    } else {
        Column(
            modifier = Modifier.fillMaxWidth()
        ){
            measures.forEachIndexed { measureIndex, measureTUOs ->
                key(measureIndex) {
                Column(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {

                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.Start,
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        measureTUOs.forEachIndexed { indexInMeasure, tuo ->
                            Column(
                                modifier = Modifier
                                    .width(gridWidthDp / gridColumnCount)
                            ) {
                                if (TimeUnitObject._hasMarker) {
                                    val lineHeight = 20.sp
                                    val density = LocalDensity.current
                                    val lineHeightDp: Dp = with(density) {
                                        lineHeight.toDp()
                                    }
                                    var fontStyle = FontStyle.Normal
                                    var fontWeight = FontWeight.Normal
                                    val hairPinSymbol = tuo.hasHairPin()
                                    val yHeight = with(density) { lineHeightDp.toPx() }

                                    if (tuo.isTriolet()) {
                                        Canvas(modifier = Modifier.fillMaxSize()) {
                                            val arcWidth = with(density) { size.width * 0.65f }
                                            drawArc(
                                                color = FEUFAROO_TRIOLET_COLOR,
                                                startAngle = 200f,
                                                sweepAngle = 140f,
                                                useCenter = false,
                                                //topLeft = Offset(0f, yHeight/* / 2*/),
                                                topLeft = Offset(size.width *0.05f, -yHeight * 0.1f),
                                                size = Size(arcWidth, yHeight),
                                                style = Stroke(width = 2f)

                                            )
                                        }
                                    }
                                    if ((hairPinSymbol == '=') && (TimeUnitObject.lastHairPinSymbol != null)) {
//                                        println("LastHairpin: ${TimeUnitObject.lastHairPinSymbol} ${TimeUnitObject.lastHairPinStart}")
                                        val hairPinStart = TimeUnitObject.lastHairPinStart
                                        val lastHairPinSymbol = TimeUnitObject.lastHairPinSymbol
                                        val hairPinStartLine: Int = (hairPinStart - 1) / gridColumnCount
                                        val hairPinEndLine: Int = (tuo.numBlock - 1) / gridColumnCount
//                                        if (hairPinStartLine == hairPinEndLine) {
                                        Canvas(
                                            modifier = Modifier.fillMaxSize()
                                        ) {
                                            val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width / 2
                                            val xEnd = if (lastHairPinSymbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairPinStart)
                                            val offsetUp = -yHeight * 0.30f
                                            val hairpinHeight = yHeight * 0.70f

                                            val yTop = 0f + offsetUp
                                            val yCenter = (hairpinHeight / 2) + offsetUp
                                            val yBottom = hairpinHeight + offsetUp

                                            drawLine(
                                                Color.DarkGray,
                                                start = Offset(x = xStart, y = yTop),
                                                end = Offset(xEnd, yCenter),
                                                strokeWidth = 0.5f
                                            )
                                            drawLine(
                                                Color.DarkGray,
                                                start = Offset(xStart, yBottom),
                                                end = Offset(xEnd, yCenter),
                                                strokeWidth = 0.5f
                                            )
                                        }
                                        TimeUnitObject.endHairPin()
//                                        }

                                    }
                                    if (hairPinSymbol != null && hairPinSymbol != '=') {
                                        TimeUnitObject.startHairPin(hairPinSymbol, tuo.numBlock)
                                    }
                                    // @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
                                    // problème si template = $QD:,-$QD
                                    tuo.pTemplate.resetCalledMarker()

                                    val musicForPtSerifBIRegex = Regex("""^(Largh\.|Grave|Largo|Lento|Adagio|And\.|Andantino|Mod\.|Moderato|Alleg\.|All\.|Viv\.|Vivacissimo|Presto|Prestiss\.|accel\.|rit\.|rall\.|riten\.|string\.|allarg\.|a tempo|Tempo I|rubato|meno mosso|più mosso|cres\.|<|decresc\.|dim\.|>|rfz|fp|pf|sub\.p|sub\.f)""",RegexOption.IGNORE_CASE)
                                    val musicForEmmentRegex = Regex("""^(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""",RegexOption.IGNORE_CASE)

                                    val markerList = tuo.pTemplate.markerToList()
                                    Row(
                                        modifier = Modifier.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
                                        verticalAlignment = Alignment.CenterVertically
                                    ) {
                                        markerList.forEach { marker ->
                                            Column {
                                                var markerFontFamily: FontFamily = FontFamily.Default
                                                var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value

                                                when {
                                                    musicForPtSerifBIRegex.matches(marker) -> {
                                                        markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value
                                                        markerFontFamily = ptSerifBoldItalic
                                                    }

                                                    musicForEmmentRegex.matches(marker) -> {
                                                        markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value + 11
                                                        markerFontFamily = emmentaler
                                                    }

                                                    else -> {
                                                        markerFontFamily = ptSerif
                                                    }
                                                }
                                                Text(
                                                    text = marker,
                                                    modifier = Modifier
                                                        .wrapContentSize(unbounded = true, align = Alignment.CenterStart,),
                                                    softWrap = false,
                                                    maxLines = 1,
                                                    fontStyle = fontStyle,
                                                    fontWeight = FontWeight.Bold,
                                                    style = MaterialTheme.typography.titleMedium.copy(
                                                        color = MaterialTheme.colorScheme.onSecondary,
                                                        fontSize = markerFontSize.sp,
                                                        fontFamily = markerFontFamily
                                                    )
                                                )
                                                if (marker != markerList.last()) {
                                                    Spacer(modifier = Modifier.width(2.dp))
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if(showFullChord) {
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.Start,
                            verticalAlignment = Alignment.CenterVertically
                        ) {
                            measureTUOs.forEachIndexed { indexInMeasure, tuo ->
                                val chordMap = (1..4).mapNotNull { voice ->
                                    val noteStr = tuo.tuNotes.getOrNull(voice)?.toString()
                                    if (noteStr != null) voice to noteStr else null
                                }.toMap()

                                val degreeName = remember(chordMap) {
                                    if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeDegree(chordMap) else null
                                }
                                val chordName = remember(chordMap) {
                                    if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeChordName(chordMap, sharedScreenModel.songKey.value) else null
                                }

                                Column(
                                    modifier = Modifier
                                        .width(gridWidthDp / gridColumnCount)
                                ) {
                                    val maxFontSize = 12.sp
                                    val minFontSize = 10.sp
                                    var currentFontSize by remember { mutableStateOf(maxFontSize) }

                                    TextButton(
                                        onClick = { toggleWithDegreeAndChord = !toggleWithDegreeAndChord },
                                        contentPadding = PaddingValues(0.dp),
                                        modifier = Modifier.height(24.dp).fillMaxWidth()
                                    ) {
                                        BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
                                            val textToDisplay = if (toggleWithDegreeAndChord) (chordName ?: "") else (degreeName ?: "")

                                            Text(
                                                text = textToDisplay,
                                                onTextLayout = { textLayoutResult ->
                                                    if (textLayoutResult.hasVisualOverflow && currentFontSize > minFontSize) {
                                                        currentFontSize = (currentFontSize.value * 0.9f).sp
                                                    }
                                                },
                                                softWrap = false,
                                                maxLines = 1,
                                                overflow = TextOverflow.Visible,
                                                style = TextStyle(
                                                    color = if (!toggleWithDegreeAndChord) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary.copy(red = MaterialTheme.colorScheme.secondary.red * 0.4f, green = MaterialTheme.colorScheme.secondary.green * 0.4f, blue = MaterialTheme.colorScheme.secondary.blue * 0.4f ),
                                                    fontWeight = FontWeight.Bold,
                                                    fontSize = currentFontSize
                                                )
                                            )
                                        }
                                    }
                                }
                            }
                        }
                    }
                    Row(modifier = Modifier.fillMaxWidth()) {
                        measureTUOs.forEachIndexed { indexInMeasure, oneTUO ->
                            val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure
                            val isActive = (globalIndex == activeRowIndex)
                            val isSelectedByKeyboard = (globalIndex == selectedGridIndex)

                            val isAbsoluteLastGrid = (globalIndex == tuoList.size - 2)
                            if (editMode && isAbsoluteLastGrid) {
                                Box(
                                    modifier = Modifier
                                        .width(gridWidthDp / gridColumnCount)
                                        .fillMaxHeight(),
                                    contentAlignment = Alignment.Center
                                ) {
                                    GridActionMenu(
                                        onAddMeasureRight = {
                                            val state = TUOEditState(
                                                tuoIndex = 9999,
                                                templateFragment = "+"
                                            )
                                            sharedScreenModel.openTUOEditor(state)
                                        },
                                        onDeleteMeasure = {
                                            val state = TUOEditState(
                                                tuoIndex = 9999,
                                                templateFragment = "-"
                                            )
                                            sharedScreenModel.openTUOEditor(state)
                                        }
                                    )
                                }
                            } else {
                            val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
                            var menuOffset by remember { mutableStateOf(Offset.Zero) }
                            val interactionSource = remember { MutableInteractionSource() }
                            val isHovered by interactionSource.collectIsHoveredAsState()

                            val hairPinState by sharedScreenModel.isPendingHairPinOnEdit.collectAsState()

                            key(oneTUO.numBlock) {
                                Box(modifier = Modifier
                                    .width(gridWidthDp / gridColumnCount)
                                    .then(
                                        if (hairPinState.isPending && hairPinState.gridIndex == globalIndex) {
                                            Modifier.background(
                                                color = MaterialTheme.colorScheme.secondary.copy(alpha = 0.3f),
                                                shape = RoundedCornerShape(4.dp)
                                            )
                                        } else {
                                            Modifier
                                        }
                                    )
                                    .border(
                                        width = if (isSelectedByKeyboard && editMode) 2.dp else 0.dp,
                                        color = if (isSelectedByKeyboard && editMode) MaterialTheme.colorScheme.error else Color.Transparent,
                                        shape = RoundedCornerShape(4.dp)
                                    )
                                    .background(
                                        if (isHovered) MaterialTheme.colorScheme.onSecondary.copy(alpha = 0.05f) else Color.Transparent,
                                        RoundedCornerShape(4.dp)
                                    )
                                    .combinedClickable(
                                        interactionSource = interactionSource,
                                        indication = LocalIndication.current,
                                        onClick = { }
                                    )
                                    .pointerInput(globalIndex) {
                                        detectTapGestures(
                                            onTap = { offset ->
                                                if(editMode) {
                                                    selectedGridIndex = globalIndex
                                                    menuOffset = offset
                                                    selectedTUO = oneTUO
                                                    selectedIndex = globalIndex
                                                    showEditDialog = true
                                                } else {
                                                    sharedScreenModel.seekToGrid(globalIndex)
                                                }
                                            },
                                            onDoubleTap = { offset ->
                                                if(editMode) {
                                                    selectedGridIndex = globalIndex
                                                    menuOffset = offset
                                                    selectedTUO = oneTUO
                                                    selectedIndex = globalIndex
                                                    showContextualMenu = true
                                                }
                                            }
                                        )
                                    }
                                ) {
                                    val currentInterval by sharedScreenModel.transpositionInterval.collectAsState()
                                    TimeUnitComposable(
                                        tuo = oneTUO,
                                        stanzaNumber = currentStanza,
                                        gridColumnCount = gridColumnCount,
                                        gridActive = isActive,
                                        isCurrentBlock = isSelectedByKeyboard && editMode,
                                        parentFocusRequester = focusRequester,
                                        transpositionInterval = currentInterval
                                    )

                                    if (showContextualMenu && selectedIndex == globalIndex) {
                                        Popup(
                                            alignment = Alignment.TopStart,
                                            offset = IntOffset(
                                                menuOffset.x.roundToInt(),
                                                menuOffset.y.roundToInt()
                                            ),
                                            onDismissRequest = { showContextualMenu = false }
                                        ) {
                                            ContextualMenu(onMenuItemClick = { item ->
                                                println("Clicked in TUO $globalIndex: $item")
                                                showContextualMenu = false
                                                when(item) {
                                                    "Modifier" -> {
                                                        showEditDialog = true
                                                    }
                                                    "Ajouter" -> {
                                                        showAddDialog = true
                                                    }
                                                    "Liste" -> {
                                                        showDetailDialog = true
                                                    }
                                                    else -> {
                                                        showDetailDialog = false
                                                    }
                                                }
                                            })
                                        }
                                    }

                                    val currentSelected = selectedTUO
                                    if ((showEditDialog || showDetailDialog) && currentSelected != null && selectedIndex == globalIndex) {
                                        val canAdd = showAddDialog
                                        val canEdit = showEditDialog || canAdd

                                        val template = oneTUO.pTemplate.template
                                        val expectedTemplate = template.count { it.isLetter() } == 2 && template.contains(".")

                                        val fileC = sharedScreenModel.fileContent.value ?: ""
                                        val lines = fileC.split("\n").toMutableList()
                                        val u0Idx = lines.indexOfFirst { it.startsWith("U0:") }
                                        val t0Idx = lines.indexOfFirst { it.startsWith("T0:") }

                                        val fullLine =  when {
                                            t0Idx != -1 -> {
                                                lines[t0Idx]
                                            }
                                            u0Idx != -1 -> {
                                                lines[u0Idx]
                                            }
                                            else -> ""
                                        }
                                        val afterU0T0 = fullLine.substringAfter("0:")
                                        val blankPrefixRegex =Regex("""z([0-9A-Z])[:]""")
                                        val match = blankPrefixRegex.find(afterU0T0)
                                        val blankPrefix = match?.groups?.get(1)?.value ?: ""

                                        val anchorsByVoice = (0..3).associate { voiceIdx ->
                                            val linePrefix = "N${voiceIdx + 1}:"
                                            val fullLine = lines.firstOrNull { it.startsWith(linePrefix) } ?: ""
                                            voiceIdx to extractAnchorFromNoteLine(fullLine)
                                        }
                                        val editState = TUOEditState(
                                            tuoIndex         = oneTUO.firstTuoIndex,
                                            notesByVoice = (0..3).associate { i ->
                                                val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
                                                val fixedNote = autoFixNote(rawNote, template)
                                                i to fixedNote
                                            },
                                            lyricsByStanza   = (1..currentStanza).associate { s ->
                                                s to oneTUO.lyricsAsMultiString(currentStanza)
                                            }.toMutableMap(),
                                                templateFragment = oneTUO.pTemplate.template,
                                            marker = listOfNotNull(
                                                oneTUO.pTemplate.markerToString().takeIf { it.isNotBlank() },
                                                oneTUO.hasHairPin()?.toString()?.takeIf { it.isNotBlank() }
                                            ).joinToString(" "),
                                            silentDuration = blankPrefix,
                                            sep = oneTUO.sep0
                                        )
                                        TUODetailDialog(
                                            editState = editState,
                                            tuo = currentSelected,
                                            currentStanza = currentStanza,
                                            menuPosition = IntOffset(
                                                menuOffset.x.roundToInt(),
                                                menuOffset.y.roundToInt()
                                            ),
                                            globalIndex = globalIndex,
                                            isEditable = canEdit,
                                            canAdd = canAdd,
                                            sharedScreenModel = sharedScreenModel,
                                            anchorsByVoice = anchorsByVoice,
                                            onDismiss = {
                                                showAddDialog = false
                                                showDetailDialog = false
                                                showEditDialog = false
                                            },
                                            onSave       = { newState ->
                                                sharedScreenModel.openTUOEditor(newState)
                                                showAddDialog    = false
                                                showEditDialog   = false
                                            },
                                            onIndexChanged = { newIndex ->
                                                selectedIndex = newIndex
                                                selectedGridIndex = newIndex
                                                val globalList = sharedScreenModel.tuoList.value
                                                val nextTUO = globalList.getOrNull(newIndex)

                                                if (nextTUO != null) {
                                                    selectedTUO = nextTUO
                                                }else {
                                                    showAddDialog = false
                                                    showDetailDialog = false
                                                    showEditDialog = false
                                                }
                                            }
                                        )
                                    }
                                }
                            }
                            }
                        }
                    }

                    val allTemps = measureTUOs.map { it.getSingleSyllable(currentStanza) }
                    val maxLines = allTemps.maxOfOrNull { it.size } ?: 0


                    val textMeasurer = rememberTextMeasurer()
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.Start
                    ) {
                        val columnWidthDp = gridWidthDp / gridColumnCount
                        val REGEX_CLEAN_PREFIX = Regex("(\\d+\\.)+")
                        val cleanAllTemps = allTemps.map { column ->
                            column.map { syllable ->
                                syllable.replace(REGEX_CLEAN_PREFIX, "").trim()
                            }
                        }
                        allTemps.forEachIndexed { syls_i, syllables ->
                            /*val fragmentU0 = measureTUOs[syls_i].pTemplate.template
                            println("Fragment=[$fragmentU0] -> Syllabes=$syllables")*/
                            Column(
                                modifier = Modifier
                                    .width(columnWidthDp)
                            ) {
                                val noteWidth = columnWidthDp
                                val density = LocalDensity.current
                                syllables.joinToString { " " }

                                val cleanSyllables = cleanAllTemps[syls_i]
                                cleanSyllables.forEachIndexed { index, syl ->
                                    var textWidth by remember { mutableStateOf(0.dp) }


                                    val textLayoutResult = textMeasurer.measure(
                                        text = syl,
                                        maxLines = 1,
                                        softWrap = false
                                    )
                                    val textWidthDp = with(density) { textLayoutResult.size.width.toDp() }
                                    val containerWidthDp = gridWidthDp / gridColumnCount
                                    val isTooLong = textWidthDp > containerWidthDp

                                    val spacer = makeSpaceBetweenSyllables(textMeasurer, columnWidthDp)
                                    val (dynamicSpaceSyl, alignmentText) = spacer(syl, cleanAllTemps, syls_i, index)
                                    Text(
                                        text = dynamicSpaceSyl,
                                        modifier = Modifier
                                            .fillMaxWidth()
                                            .padding(end = 4.dp)
                                            .wrapContentSize(unbounded = true, align = alignmentText),
                                        softWrap = false,
                                        maxLines = 1,
                                        overflow = TextOverflow.Visible,
                                        style = TextStyle(
                                            color = when {
                                                (isTooLong) -> MaterialTheme.colorScheme.onBackground
                                                (index == 0) -> MaterialTheme.colorScheme.onBackground
                                                else -> MaterialTheme.colorScheme.onBackground
                                            },
                                            fontSize = if(isAnySyllableTooLong) MaterialTheme.typography.labelLarge.fontSize else MaterialTheme.typography.titleMedium.fontSize
                                        ),
                                        onTextLayout = { txtLayoutRes ->
                                            textWidth = with(density) { txtLayoutRes.size.width.toDp() }
                                            val textWidthPx = txtLayoutRes.size.width
                                            val noteWidthPx = with(density) { noteWidth.toPx() }
                                        }
                                    )
                                }
                            }
                        }
                    }
                }
            }
            }
        }
    }
    }
}
}

@Composable
fun makeSpaceBetweenSyllables(
    textMeasurer: TextMeasurer,
    noteWidthDp: Dp,
    fontSize: TextUnit = 16.sp
): (String, List<List<String>>, Int, Int) -> Pair<String,  Alignment> {
    val density = LocalDensity.current
    val noteWidthPx = with(density) { noteWidthDp.toPx() }
    val style = TextStyle(fontSize = fontSize)
    val spaceWidthPx = textMeasurer.measure("\u00A0", style).size.width


    return { currentSyl, allT, s_i, index ->
        var textAlign = Alignment.Center
        if (currentSyl.isEmpty()) "" to textAlign
        else {
            val currentWidth = textMeasurer.measure(currentSyl, style).size.width
            val next_syl = allT.getOrNull(s_i + 1)?.getOrNull(index) ?: ""
            val prev_syl = if (s_i > 0) allT.getOrNull(s_i - 1)?.getOrNull(index) ?: "" else ""

            val excessPx = currentWidth - noteWidthPx
            val isExcess = (excessPx > 0)
            val neededSpaces = if (isExcess) (excessPx / spaceWidthPx).toInt() + 1 else 0

            val nextNextSyl = allT.getOrNull(s_i + 2)?.getOrNull(index) ?: ""
        //  println("801: prev: [$prev_syl]${prev_syl.length}:[${textMeasurer.measure(prev_syl, style).size.width}], current [$currentSyl]:[${textMeasurer.measure(currentSyl, style).size.width}], next [$next_syl]:[${textMeasurer.measure(next_syl, style).size.width}], isExcess=$excessPx & need $neededSpaces \t spwp $spaceWidthPx\n")

            val totalSpacesPossible = (noteWidthPx / spaceWidthPx).toInt()
            val paddingNeeded = totalSpacesPossible - (currentSyl.length+4)
            val resultText = when {
                !isExcess && (paddingNeeded > 0)  -> {
                    textAlign = Alignment.TopStart
                    currentSyl + "\u00A0".repeat(paddingNeeded)
                }
                isExcess && next_syl.length < prev_syl.length -> {
                    "\u00A0".repeat(neededSpaces) + currentSyl
                }
                isExcess && next_syl.length >= prev_syl.length -> {
                    currentSyl + "\u00A0".repeat(neededSpaces)
                }

                isExcess && next_syl.isNotEmpty() -> {
                    val nextWidth = textMeasurer.measure(next_syl, style).size.width
                    val nextExcess = nextWidth - noteWidthPx

                    if (nextExcess > 0 && (allT.getOrNull(s_i + 2)?.getOrNull(index)?.length ?: 0) > next_syl.length) {
                        // Si le mot d'après va se décaler à gauche, moi je me décale aussi à gauche
                        val compensationSpaces = ((nextExcess / spaceWidthPx) / 2).toInt() + 1
                        "\u00A0".repeat(compensationSpaces) + currentSyl
                    } else {
                        currentSyl
                    }
                }

                else -> currentSyl
            }
            resultText to textAlign
        }
    }
}
private suspend fun expandInclusions(
    content: String,
    currentFilePath: String,
    fileRepository: FileRepository
): String {
    val lines = content.split("\n")
    val result = StringBuilder()
    val lastSlash = currentFilePath.lastIndexOf('/')
    val directory = if (lastSlash != -1) currentFilePath.substring(0, lastSlash + 1) else ""

    val inclusionRegex = Regex("^I([0-9]):(.*)")

    lines.forEach { line ->
        val match = inclusionRegex.find(line)

        if (match != null) {
            try {
                val body = match.groupValues[2]
                val parts = body.split(":")
                val fileName = parts[0]
                val ignorePattern = if (parts.size > 1) parts[1] else ""

                val fullPath = directory + fileName
                val includedLines = fileRepository.readFileLines(fullPath)

                val regexIgnore = if (ignorePattern.isNotEmpty()) Regex(ignorePattern) else null

                includedLines.forEach { incLine ->
                    if (regexIgnore == null || !regexIgnore.containsMatchIn(incLine)) {
                        result.append(incLine).append("\n")
                    }
                }
            } catch (e: Exception) {
                result.append("// Erreur inclusion sur ${line}: ${e.message}\n")
            }
        } else {
            result.append(line).append("\n")
        }
    }
    return result.toString().trimEnd()
}

@Composable
fun EditSourceCompose(
    sharedScreenModel: SharedScreenModel,
    solfaScreenModel: SolfaScreenModel
) {
    var isEditorVisible = sharedScreenModel.editModeState.value
    var sourcePath = sharedScreenModel.activeFilePath.value
    var sourceContent = sharedScreenModel.fileContent.value ?: ""
    var sourceTitle = sharedScreenModel.songTitle.value

//    println("path $sourcePath\n" +
//            "contenu $sourceContent")
    val fileNam = sourcePath.substringAfterLast('/').substringAfterLast(':')
    var originalPath by remember(fileNam) { mutableStateOf(sourcePath) }
    LaunchedEffect(sourcePath) {
        if (sourcePath.isNotEmpty() && !sourcePath.contains("/tmp/") && !sourcePath.contains("tmp") && !sourcePath.contains("_tmp")) {
            originalPath = sourcePath
        }
    }

    val fileName = originalPath.substringAfterLast('/').substringAfterLast(':')
    val scope = rememberCoroutineScope()

    var currentTempFile by remember { mutableStateOf<File?>(null) }

    var codeContent by remember { mutableStateOf(sourceContent?: "") }

    val pathValidator = remember { getPathValidator() }
    // restricted path pour les fichiers interne de l'app
    val isRestrictedPath = remember(originalPath) {
        pathValidator.isRestrictedPath(originalPath)
    }

    val isModified = remember(codeContent, sourceContent) { codeContent != sourceContent }

    var previousCodeContentBeforeBuild by remember { mutableStateOf<String?>(null) }

    LaunchedEffect(sourceContent) {
        codeContent = sourceContent
        previousCodeContentBeforeBuild = null
    }
    var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) }


    val saveLauncher = rememberFileSaveLauncher { chosenPath ->
        if (chosenPath != null) {
            val contentToSave = codeContent
            scope.launch(Dispatchers.IO) {
                try {
                    val data = codeContent.toByteArray(Charsets.UTF_8)
                    solfaScreenModel.fileRepository.saveLocalFile(chosenPath, data)
                    withContext(Dispatchers.Main) {
                        println("Fichier sauvegardé avec succès")
                        solfaScreenModel.loadExternalFile(chosenPath)
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        } else {
            println("Sauvegarde annulée par l'utilisateur")
        }
    }

    if(sourcePath.isNotEmpty() && sourceContent.isNotEmpty()) {
        Column(modifier = Modifier.fillMaxSize()) {
            if (isEditorVisible) {
                Card(
                    modifier = Modifier.fillMaxSize().padding(4.dp),
                    shape = RoundedCornerShape(12.dp),
                    elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
                ) {
                    Column {
                        Row(
                            Modifier.fillMaxWidth().background(Color(0xFFF5F5F5)).padding(2.dp),
                            verticalAlignment = Alignment.CenterVertically
                        ) {
                            Icon(Icons.Default.Description, contentDescription = null, modifier = Modifier.size(16.dp))
                            Text(
                                text = sourceTitle.ifEmpty { "nouveau_feufaro.txt" },
                                modifier = Modifier.padding(start = 8.dp).widthIn(max = 160.dp),
                                style = TextStyle(fontSize = 12.sp, fontWeight = FontWeight.Medium)
                            )
                            Spacer(modifier = Modifier.weight(1f))

                            EditorActionButtons(
                                isUndoVisible = isModified,
                                onUndo = {
                                    scope.launch {
                                        codeContent = sourceContent

                                        withContext(Dispatchers.Main) {
                                            solfaScreenModel.loadExternalFile(originalPath)
                                        }
                                        currentTempFile?.delete()
                                        currentTempFile = null
                                    }
                                },
                                onBuild = {
                                    scope.launch {
                                        try {
                                            val expandedContent = expandInclusions(
                                                codeContent,
                                                sourcePath,
                                                solfaScreenModel.fileRepository
                                            )

                                            if (currentTempFile == null || !currentTempFile!!.exists()) {
                                                val tempDir = System.getProperty("java.io.tmpdir")
                                                currentTempFile = File(tempDir, fileName)
                                                currentTempFile?.deleteOnExit()
                                            }

                                            withContext(Dispatchers.IO) {
                                                currentTempFile?.writeText(expandedContent)
                                            }

                                            withContext(Dispatchers.Main) {
                                                solfaScreenModel.justCompile(currentTempFile!!.absolutePath, expandedContent)

                                                println("Compilation  : ${currentTempFile?.absolutePath}")
                                                currentTempFile?.deleteOnExit()
                                            }

                                        } catch (e: Exception) {
                                            e.printStackTrace()
                                        }
                                    }
                                },
                                onSave = {
                                    scope.launch {
                                        try {
                                            if (!isRestrictedPath) {
                                                val externalFile = File(originalPath)
                                                withContext(Dispatchers.IO) {
                                                    externalFile.writeText(codeContent)
                                                }

                                                withContext(Dispatchers.Main) {
                                                    sharedScreenModel.setFileContent(codeContent, originalPath)
                                                    solfaScreenModel.loadExternalFile(originalPath)
                                                }
                                                println("Source écrasé : $originalPath")
                                            } else {
                                                val initialDir = solfaScreenModel.fileRepository.getAppPublicFolder().absolutePath
                                                saveLauncher.launch(fileName, initialDir)
                                            }
                                            currentTempFile?.delete()
                                            currentTempFile = null
                                        } catch (e: Exception) {
                                            e.printStackTrace()
                                        }
                                    }
                                 },
                                onClose = { sharedScreenModel.toggleEditMode() }
                            )
                        }

                        Row(modifier = Modifier.fillMaxSize().background(Color.White)) {
                            BasicTextField(
                                value = codeContent,
                                onValueChange = { codeContent = it },
                                keyboardOptions = KeyboardOptions(
                                    autoCorrectEnabled = false,
                                    keyboardType = KeyboardType.Ascii
                                ),
                                modifier = Modifier.fillMaxSize().padding(start = 8.dp, top = 8.dp),
                                textStyle = TextStyle(
                                    fontFamily = FontFamily.Monospace,
                                    fontSize = 13.sp,
                                    lineHeight = 20.sp
                                ),
                                visualTransformation = SolfaVisualTransformation(),
                                cursorBrush = SolidColor(Color.Black)
                            )
                        }
                    }
                }
            }
        }
    }
}

@Composable
fun EditorActionButtons(isUndoVisible: Boolean, onUndo: () -> Unit, onBuild: () -> Unit, onSave: () -> Unit, onClose: () -> Unit) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        if(isUndoVisible) {
            IconButton(onClick = onUndo) { Icon(Icons.AutoMirrored.Filled.Undo, null, tint = Color.Gray) }
        }
        IconButton(onClick = onBuild) { Icon(Icons.Default.Build, null, tint = Color(0xFF2196F3)) }
        IconButton(onClick = onSave) { Icon(Icons.Default.Save, null, tint = Color(0xFF4CAF50)) }
        IconButton(onClick = onClose) { Icon(Icons.Default.Close, null, tint = Color.Red) }
    }
}

fun autoFixNote(rawNote: String, template: String): String {
//    println("\n==================================================")
//    println(" ENTRÉE autoFixNote")
//    println("  • Template : \"$template\"")
//    println("  • RawNote  : \"$rawNote\"")
//    println("==================================================")
    if (rawNote == "_") return rawNote

    val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*")
    val actualNotes = if (rawNote.isEmpty()) emptyList() else noteRegex.findAll(rawNote).map { it.value }.toList()
//    println("  • Notes détectées dans RawNote : $actualNotes (Taille: ${actualNotes.size})")
    /*if (rawNote.contains("•") || rawNote.contains("―") || rawNote.contains(",")) {
        val templateNotesCount = template.count { c ->
            c in 'A'..'Y' || c in 'a'..'y'
        }
        if (actualNotes.size >= templateNotesCount) return rawNote
    }*/
    val requiredNotesCount = template.count { it in 'A'..'Y' || it in 'a'..'y' }

    val shouldIncludeParentheses = actualNotes.size >= requiredNotesCount
//    println("  • Notes requises par le template : $requiredNotesCount")
//    println("  • Inclusion des parenthèses autorisée ? : $shouldIncludeParentheses")
//    println("--------------------------------------------------")
    val result = StringBuilder()
    var noteIdx = 0

    for (i in template.indices) {
        val char = template[i]
//        print("[Étape $i] Caractère template = '$char' | Index note actuel = $noteIdx -> ")
        when {
            (char in 'A'..'Y' || char in 'a'..'y') -> {
                if (noteIdx < actualNotes.size) {
                    val noteToAdd = actualNotes[noteIdx]
                    if (noteIdx > 0 && result.isNotEmpty()) {
                        val lastChar = result.last()
//                        println()
//                        println("    [INFO ESPACE] lastC '$lastChar' avant d'ajouter la note \"$noteToAdd\"")
                        /* if (lastChar != '•' && lastChar != ',' && lastChar != '(' && lastChar != ')') {
                            result.append(" ")
                        } */
//                        println("    [INFO ESPACE] Maintenant res est \"${result.toString()}\"")
//                        print("    -> Continuité Étape $i : ")
                    }

                    result.append(noteToAdd)
//                    println("Action : Ajout de la note utilisateur \"$noteToAdd\"")
                    noteIdx++
                } else {
                    /*if (rawNote.isEmpty()) {*/
                    result.append("z")
                    /*} else {
                        result.append("―")
                    }*/
//                    println("Action : Plus de notes utilisateur ! Ajout du silence par défaut \"z\"")
                }
            }
            char == '-' -> {
                if (noteIdx < actualNotes.size && actualNotes[noteIdx] == "―") {
                    result.append("―")
//                    println("Action : Consommation et ajout du tiret utilisateur \"―\"")
                    noteIdx++
                } else {
                    val prev = if (i > 0) template[i - 1] else null
                    val next = if (i < template.lastIndex) template[i + 1] else null
                    if (prev == '.' && next == ',') {
                        result.append(" ")
//                        println("Action : Structure .-, détectée -> Ajout d'un espace \" \"")
                    } else {
                        result.append("―")
//                        println("Action : Ajout du tiret automatique \"―\"")
                    }
                }
            }
            char == 'z' -> {
                result.append(" ")
//                println("Action : Caractère 'z' du template -> Ajout d'un espace \" \"")
            }
            char == '.' -> {
                val currentText = result.toString()
                if (!currentText.endsWith("•")) {
                    result.append("•")
//                    println("Action : Ajout du point musical \"•\"")
                } else {
//                    println("Action : Ignoré (se termine déjà par un point)")
                }
            }
            char == ',' -> {
                val currentText = result.toString()
                if (!currentText.endsWith(",")) {
                    result.append(",")
//                    println("Action : Ajout de la virgule \",\"")
                } else {
//                    println("Action : Ignoré (se termine déjà par une virgule)")
                }
            }
            char == '(' -> {
                if (shouldIncludeParentheses) {
                    result.append("(")
//                    println("Action : Ajout parenthèse ouvrante \"(\"")
                } else {
//                    println("Action : Ignoré (pas assez de notes)")
                }
            }
            char == ')' -> {
                if (shouldIncludeParentheses) {
                    result.append(")")
//                    println("Action : Ajout parenthèse fermante \")\"")
                } else {
//                    println("Action : Ignoré (pas assez de notes)")
                }
            }
            else -> {
//                println("Action : Aucun comportement défini pour '$char'")
            }
        }
//        println("    ↳ État actuel du buffer : \"${result.toString()}\"")
    }
    var finalResult = result.toString()
//    println("  • Avant formatage final : \"$finalResult\"")

    finalResult = finalResult
        .replace("( ", "(")
        .replace(" )", ")")
        .replace("•,", "• ,")

//    println("on a RES ${finalResult}")
    return finalResult
}
private fun transformMusicalInput(input: String): String {
    return input.lowercase()
        .replace(";", "• ,")
        .replace(".", "•")
        .replace("-", "―")
        .replace("(?<=[a-z])[']".toRegex(), "¹")
        .replace("(?<=[a-z])[,]".toRegex(), "₁")
        .replace("¹¹", "²")
        .replace("¹'", "²")
        .replace("²¹", "³")
        .replace("²'", "³")
        .replace("₁,", "₂")
        .replace("₁₁", "₂")
        .replace("₂,", "₃")
        .replace("₂₁", "₃")
        .replace("'", "¹")
}

fun extractAnchorFromNoteLine(noteLine: String): String? {
    val match = Regex("#[drmfsltDRFSTz][',]*").find(noteLine)
    return match?.value
}