package com.example.notesai.presentation.screens.note.editor

import androidx.compose.runtime.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.input.TextFieldValue

@Stable
class RichEditorState(initial: AnnotatedString) {

    var value by mutableStateOf(
        TextFieldValue(
            annotatedString = initial,
            selection = TextRange(initial.length)
        )
    )

    /* =====================
       UNDO / REDO
       ===================== */
    private val undoStack = mutableStateListOf<TextFieldValue>()
    private val redoStack = mutableStateListOf<TextFieldValue>()

    private fun snapshot() {
        undoStack.add(value)
        redoStack.clear()
    }

    fun canUndo() = undoStack.isNotEmpty()
    fun canRedo() = redoStack.isNotEmpty()

    fun undo() {
        if (!canUndo()) return
        redoStack.add(value)
        value = undoStack.removeAt(undoStack.lastIndex)
    }

    fun redo() {
        if (!canRedo()) return
        undoStack.add(value)
        value = redoStack.removeAt(redoStack.lastIndex)
    }

    /* =====================
       STICKY TYPING STYLE
       ===================== */
    private val activeStyles = mutableStateListOf<SpanStyle>()

    /* =====================
       VALUE CHANGE
       ===================== */
    fun onValueChange(newValue: TextFieldValue) {
        val old = value

        // cursor/selection change only
        if (newValue.text == old.text) {
            value = old.copy(
                selection = newValue.selection,
                composition = newValue.composition
            )
            return
        }

        snapshot()

        // Build new annotated string by preserving old spans
        val built = buildPreservingSpans(old, newValue)

        // Auto-convert markdown patterns
        val converted = autoConvertMarkdown(built)

        value = converted
    }

    private fun buildPreservingSpans(old: TextFieldValue, newValue: TextFieldValue): TextFieldValue {
        val builder = AnnotatedString.Builder(newValue.text)

        val oldText = old.text
        val newText = newValue.text

        // Detect where text was inserted/deleted
        val oldLen = oldText.length
        val newLen = newText.length
        val delta = newLen - oldLen

        // Find insertion/deletion point
        var changeStart = 0
        while (changeStart < minOf(oldLen, newLen) &&
            oldText.getOrNull(changeStart) == newText.getOrNull(changeStart)) {
            changeStart++
        }

        val changeEnd = changeStart + maxOf(0, -delta)

        // Copy and adjust old spans
        old.annotatedString.spanStyles.forEach { r ->
            var start = r.start
            var end = r.end

            // Adjust span positions based on where text changed
            when {
                // Span is completely before change
                end <= changeStart -> {
                    // No adjustment needed
                }
                // Span is completely after change
                start >= changeEnd -> {
                    start += delta
                    end += delta
                }
                // Span contains the change point
                start < changeStart && end > changeEnd -> {
                    end += delta
                }
                // Span starts before change, ends in change area
                start < changeStart && end in (changeStart + 1)..changeEnd -> {
                    end = changeStart
                }
                // Span starts in change area, ends after
                start in changeStart until changeEnd && end >= changeEnd -> {
                    start = changeStart + delta
                    end += delta
                }
                // Span is completely inside change area - skip it
                else -> return@forEach
            }

            // Clamp to valid range
            start = start.coerceIn(0, newLen)
            end = end.coerceIn(0, newLen)

            if (start < end) {
                builder.addStyle(r.item, start, end)
            }
        }

        // Apply sticky styles to newly inserted character(s)
        if (delta > 0 && activeStyles.isNotEmpty()) {
            val insertStart = changeStart
            val insertEnd = (changeStart + delta).coerceIn(0, newLen)
            if (insertStart < insertEnd) {
                activeStyles.forEach { st ->
                    builder.addStyle(st, insertStart, insertEnd)
                }
            }
        }

        return TextFieldValue(
            annotatedString = builder.toAnnotatedString(),
            selection = newValue.selection,
            composition = newValue.composition
        )
    }

    /* =====================
       TOOLBAR TOGGLES
       ===================== */
    fun toggleBold() = toggleStyle(SpanStyle(fontWeight = FontWeight.Bold))
    fun toggleItalic() = toggleStyle(SpanStyle(fontStyle = FontStyle.Italic))
    fun toggleUnderline() = toggleStyle(SpanStyle(textDecoration = TextDecoration.Underline))

    private fun toggleStyle(style: SpanStyle) {
        val sel = value.selection.normalized()
        snapshot()

        if (!sel.collapsed) applyStyleToSelection(style)
        else toggleTypingStyle(style)
    }

    private fun toggleTypingStyle(style: SpanStyle) {
        val idx = activeStyles.indexOfFirst { it.hasSameStyle(style) }
        if (idx >= 0) activeStyles.removeAt(idx) else activeStyles.add(style)
    }

    private fun applyStyleToSelection(style: SpanStyle) {
        val sel = value.selection.normalized()
        val start = sel.start
        val end = sel.end
        if (start >= end) return

        val builder = AnnotatedString.Builder(value.text)

        value.annotatedString.spanStyles.forEach { r ->
            val overlap = r.start < end && r.end > start
            val same = r.item.hasSameStyle(style)
            if (!(overlap && same)) builder.addStyle(r.item, r.start, r.end)
        }

        builder.addStyle(style, start, end)

        value = value.copy(
            annotatedString = builder.toAnnotatedString(),
            selection = TextRange(end)
        )
    }

    /* =====================
       AUTO-CONVERT MARKDOWN
       ===================== */
    private fun autoConvertMarkdown(v: TextFieldValue): TextFieldValue {
        var cur = v

        // Bold before italic to avoid conflicts
        cur = convertBold(cur)
        cur = convertItalic(cur)

        return cur
    }

    // **word** -> bold(word), remove ** **
    private fun convertBold(v: TextFieldValue): TextFieldValue {
        val text = v.text
        val cursor = v.selection.start
        if (cursor < 2) return v
        if (!(text.getOrNull(cursor - 1) == '*' && text.getOrNull(cursor - 2) == '*')) return v

        val startMarker = text.lastIndexOf("**", startIndex = cursor - 3)
        if (startMarker == -1) return v
        val contentStart = startMarker + 2
        val contentEnd = cursor - 2
        if (contentEnd <= contentStart) return v
        if (text.substring(contentStart, contentEnd).contains('\n')) return v

        // Remove end marker then start marker
        var out = v
        out = replaceTextPreserveSpansLocal(out, cursor - 2, cursor, "")
        out = replaceTextPreserveSpansLocal(out, startMarker, startMarker + 2, "")

        // Apply bold style
        val newStart = startMarker
        val newEnd = contentEnd - 2
        out = addStylePreserve(out, SpanStyle(fontWeight = FontWeight.Bold), newStart, newEnd)

        // Adjust cursor
        out = out.copy(selection = TextRange((cursor - 4).coerceAtLeast(newEnd)))
        return out
    }

    // *word* -> italic(word), remove * *
    private fun convertItalic(v: TextFieldValue): TextFieldValue {
        val text = v.text
        val cursor = v.selection.start
        if (cursor < 1) return v
        // Avoid triggering on bold closing (**)
        if (text.getOrNull(cursor - 1) != '*') return v
        if (text.getOrNull(cursor - 2) == '*') return v

        val startMarker = text.lastIndexOf('*', startIndex = cursor - 2)
        if (startMarker == -1) return v
        // Avoid ** as start
        if (text.getOrNull(startMarker - 1) == '*') return v

        val contentStart = startMarker + 1
        val contentEnd = cursor - 1
        if (contentEnd <= contentStart) return v
        if (text.substring(contentStart, contentEnd).contains('\n')) return v

        var out = v
        out = replaceTextPreserveSpansLocal(out, cursor - 1, cursor, "")
        out = replaceTextPreserveSpansLocal(out, startMarker, startMarker + 1, "")

        val newStart = startMarker
        val newEnd = contentEnd - 1
        out = addStylePreserve(out, SpanStyle(fontStyle = FontStyle.Italic), newStart, newEnd)
        out = out.copy(selection = TextRange((cursor - 2).coerceAtLeast(newEnd)))
        return out
    }

    /* =====================
       TOOLBAR STATE
       ===================== */
    fun isBoldActive() = isStyleActive(fontWeight = FontWeight.Bold)
    fun isItalicActive() = isStyleActive(fontStyle = FontStyle.Italic)
    fun isUnderlineActive() = isStyleActive(decoration = TextDecoration.Underline)

    private fun isStyleActive(
        fontWeight: FontWeight? = null,
        fontStyle: FontStyle? = null,
        decoration: TextDecoration? = null
    ): Boolean {
        val sel = value.selection
        if (!sel.collapsed) {
            return value.annotatedString.spanStyles.any {
                it.start <= sel.start &&
                        it.end >= sel.end &&
                        (fontWeight == null || it.item.fontWeight == fontWeight) &&
                        (fontStyle == null || it.item.fontStyle == fontStyle) &&
                        (decoration == null || it.item.textDecoration == decoration)
            }
        }
        return activeStyles.any {
            (fontWeight == null || it.fontWeight == fontWeight) &&
                    (fontStyle == null || it.fontStyle == fontStyle) &&
                    (decoration == null || it.textDecoration == decoration)
        }
    }

    /* =====================
       INTERNAL: text replace while preserving spans
       ===================== */
    private fun replaceTextPreserveSpansLocal(
        v: TextFieldValue,
        start: Int,
        end: Int,
        replacement: String
    ): TextFieldValue {
        val oldText = v.text
        val s = start.coerceIn(0, oldText.length)
        val e = end.coerceIn(0, oldText.length)
        if (s > e) return v

        val newText = oldText.substring(0, s) + replacement + oldText.substring(e)
        val delta = replacement.length - (e - s)

        val b = AnnotatedString.Builder(newText)
        v.annotatedString.spanStyles.forEach { r ->
            var rs = r.start
            var re = r.end

            // Adjust spans
            when {
                re <= s -> Unit
                rs >= e -> { rs += delta; re += delta }
                rs < s && re > e -> re += delta
                rs < s && re in (s + 1)..e -> re = s
                rs in s until e && re > e -> { rs = s + replacement.length; re += delta }
                else -> return@forEach
            }

            rs = rs.coerceIn(0, newText.length)
            re = re.coerceIn(0, newText.length)
            if (rs < re) b.addStyle(r.item, rs, re)
        }

        return v.copy(annotatedString = b.toAnnotatedString())
    }

    private fun addStylePreserve(v: TextFieldValue, style: SpanStyle, start: Int, end: Int): TextFieldValue {
        val s = start.coerceIn(0, v.text.length)
        val e = end.coerceIn(0, v.text.length)
        if (s >= e) return v

        val b = AnnotatedString.Builder(v.text)
        v.annotatedString.spanStyles.forEach { r -> b.addStyle(r.item, r.start, r.end) }
        b.addStyle(style, s, e)
        return v.copy(annotatedString = b.toAnnotatedString())
    }
}

/* =====================
   HELPERS
   ===================== */
private fun SpanStyle.hasSameStyle(other: SpanStyle): Boolean =
    fontWeight == other.fontWeight &&
            fontStyle == other.fontStyle &&
            textDecoration == other.textDecoration

private fun TextRange.normalized(): TextRange =
    if (start <= end) this else TextRange(end, start)