package mg.dot.feufaro.solfa

import androidx.compose.foundation.background
import androidx.compose.runtime.Composable
import androidx.compose.foundation.layout.*
import androidx.compose.ui.Modifier
import androidx.compose.material3.Text
import androidx.compose.material3.LocalTextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import mg.dot.feufaro.data.GridTUOData
import kotlin.math.min
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.withStyle
import SharedScreenModel
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.style.TextAlign
import mg.dot.feufaro.viewmodel.MidiMarkers

val FEUFAROO_TRIOLET_COLOR = Color.DarkGray
val FEUFAROO_KEY_CHANGE_COLOR = Color.Blue
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 = ""
    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
) {
    val col = if (tuo.getNum() % 2 == 0) Color(0xff, 0xfa, 0xf7) else  Color(0xfb, 0xf3, 0xff)
    val currentDensity = LocalDensity.current

    val animatedColor by animateColorAsState(
        targetValue = if (gridActive) Color.Cyan.copy(alpha = 0.5f) else col,
        animationSpec = tween(durationMillis = 100) // Très court pour rester réactif
    )
    Column(
        modifier = Modifier
            .background(/*if(gridActive) Color.Cyan.copy(alpha = 0.5f) else col*/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 multiLineText = tuo.noteAsMultiString()
            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 = FEUFAROO_KEY_CHANGE_COLOR
                                )) {
                                    append(text+" ")
                                }
                        }

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

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

    var maxWidth by remember { mutableStateOf(0.dp) }
    LaunchedEffect(items) {
        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
            }
        }
    }
    return maxWidth + 13.dp
}
@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 regexMeasure = Regex("(\\d)/\\d").find(viewModel.measure)
    val tuoList  = viewModel.tuoList

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

        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.collectAsState()

        val measures = tuoList.drop(1).chunked(gridColumnCount)
        // Avant column affichage:
        val metadataList = remember(tuoList) {
            tuoList.drop(1).mapIndexedNotNull { globalIndex, oneTUO ->
                val markerText = oneTUO.pTemplate.markerToString()
                val hairPin = oneTUO.hasHairPin()
                val finalMarker = when {
                    hairPin != null && markerText.isBlank()    -> hairPin.toString()
                    hairPin != null && markerText.isNotBlank() -> "${markerText.trim()}$hairPin"
                    markerText.isNotBlank()                    -> markerText
                    else                                       -> null
                }
                if (finalMarker != null) {
                    val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
//                    println("MetaData[$globalIndex] marker='$finalMarker' hairpin=$hairPin")
                    MidiMarkers(
                        myTimestamp,
                        globalIndex,
                        oneTUO.pTemplate.template,
                        oneTUO.pTemplate.lastCalledMarker,
                        finalMarker,
                        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)
        }
    }
        Column(
            modifier = Modifier.fillMaxWidth()
        ){
            measures.forEachIndexed { measureIndex, measureTUOs ->
                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.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(Regex("^\\s*(ppp|pp|p|mp|mf|f|ff|fff)\\s*$"))) {
                                        fontWeight = FontWeight.Bold
                                    }
                                    Text(text =  text,
                                        modifier = Modifier
                                        .wrapContentSize(unbounded = true, align = Alignment.CenterStart,),
                                        softWrap = false,
                                        maxLines = 1,
                                        fontStyle = fontStyle,
                                        fontWeight = fontWeight,
                                        style = TextStyle(
                                            color = Color.Black,
                                            fontSize = 17.sp
                                        )
                                    )
                                }
                            }
                        }
                    }
                    Row(modifier = Modifier.fillMaxWidth()) {
                        measureTUOs.forEachIndexed { indexInMeasure, oneTUO ->
                            val globalIndex = (measureIndex * gridColumnCount) + indexInMeasure
                            val isActive = (globalIndex == activeRowIndex)

                            val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }

                            Box(modifier = Modifier.width(gridWidthDp / gridColumnCount)
                            .combinedClickable(
                                onClick = {
//                                    println("je suis sur grille no: $globalIndex")
                                    sharedScreenModel.seekToGrid(globalIndex)
                                } ,
                                onDoubleClick = {
                                    val m = sharedScreenModel.getFullMarkers()
                                    m.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
                                        println("Allmarker $marker in $gridIndex on $timestamp ms")
                                        // & note= $note & nBefore =$noteBefore & separateur = $separat
                                    }
                                }
                            )) {
                                TimeUnitComposable(
                                    tuo = oneTUO,
                                    stanzaNumber = currentStanza,
                                    gridColumnCount = gridColumnCount,
                                    gridActive = isActive
                                )
                            }
                        }
                    }

                    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 ->
                            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)/*.border(1.dp, Color.Yellow, RectangleShape)*/
                                            .wrapContentSize(unbounded = true, align = alignmentText),
                                        softWrap = false,
                                        maxLines = 1,
                                        overflow = TextOverflow.Visible,
                                        style = TextStyle(
                                            color = when {
                                                (isTooLong) -> Color(0XFF121212)
                                                (index == 0) -> Color.Black
                                                else -> Color(0XFF3B3A39)
                                            },
                                            fontSize = 16.sp,
                                        ),
                                        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
        }
    }
}