// Pure Lux markdown-to-HTML converter
// Handles block-level and inline-level markdown elements

// === Inline processing ===

// Process inline markdown: **bold**, *italic*/_italic_, `code`, [links](url)
// Uses index-based scanning
pub fn processInline(text: String): String = {
    let len = String.length(text);
    processInlineFrom(text, 0, len, "")
}

fn processInlineFrom(text: String, i: Int, len: Int, acc: String): String = {
    if i >= len then acc
    else {
        let ch = String.substring(text, i, i + 1);
        // ** bold **
        if ch == "*" then
            if i + 1 < len then
                if String.substring(text, i + 1, i + 2) == "*" then
                    // Look for closing **
                    match findClosing(text, i + 2, len, "**") {
                        Some(end) => {
                            let inner = String.substring(text, i + 2, end);
                            processInlineFrom(text, end + 2, len, acc + "<strong>" + processInline(inner) + "</strong>")
                        },
                        None => processInlineFrom(text, i + 1, len, acc + ch)
                    }
                else
                    // Single * italic
                    match findClosing(text, i + 1, len, "*") {
                        Some(end) => {
                            let inner = String.substring(text, i + 1, end);
                            processInlineFrom(text, end + 1, len, acc + "<em>" + processInline(inner) + "</em>")
                        },
                        None => processInlineFrom(text, i + 1, len, acc + ch)
                    }
            else
                acc + ch
        // _italic_
        else if ch == "_" then
            if i + 1 < len then
                match findClosing(text, i + 1, len, "_") {
                    Some(end) => {
                        let inner = String.substring(text, i + 1, end);
                        processInlineFrom(text, end + 1, len, acc + "<em>" + processInline(inner) + "</em>")
                    },
                    None => processInlineFrom(text, i + 1, len, acc + ch)
                }
            else
                acc + ch
        // `code`
        else if ch == "`" then
            match findClosing(text, i + 1, len, "`") {
                Some(end) => {
                    let inner = String.substring(text, i + 1, end);
                    processInlineFrom(text, end + 1, len, acc + "<code>" + inner + "</code>")
                },
                None => processInlineFrom(text, i + 1, len, acc + ch)
            }
        // [text](url)
        else if ch == "[" then
            match findClosing(text, i + 1, len, "](") {
                Some(end) => {
                    let linkText = String.substring(text, i + 1, end);
                    match findClosing(text, end + 2, len, ")") {
                        Some(urlEnd) => {
                            let url = String.substring(text, end + 2, urlEnd);
                            processInlineFrom(text, urlEnd + 1, len, acc + "<a href=\"" + url + "\">" + processInline(linkText) + "</a>")
                        },
                        None => processInlineFrom(text, i + 1, len, acc + ch)
                    }
                },
                None => processInlineFrom(text, i + 1, len, acc + ch)
            }
        // ![alt](url) — images
        else if ch == "!" then
            if i + 1 < len then
                if String.substring(text, i + 1, i + 2) == "[" then
                    match findClosing(text, i + 2, len, "](") {
                        Some(end) => {
                            let alt = String.substring(text, i + 2, end);
                            match findClosing(text, end + 2, len, ")") {
                                Some(urlEnd) => {
                                    let url = String.substring(text, end + 2, urlEnd);
                                    processInlineFrom(text, urlEnd + 1, len, acc + "<img src=\"" + url + "\" alt=\"" + alt + "\">")
                                },
                                None => processInlineFrom(text, i + 1, len, acc + ch)
                            }
                        },
                        None => processInlineFrom(text, i + 1, len, acc + ch)
                    }
                else
                    processInlineFrom(text, i + 1, len, acc + ch)
            else
                acc + ch
        // HTML entities: & < >
        else if ch == "&" then
            processInlineFrom(text, i + 1, len, acc + "&amp;")
        else if ch == "<" then
            // Check if this looks like an HTML tag — pass through
            if i + 1 < len then
                if isHtmlTagStart(text, i, len) then {
                    // Find closing >
                    match findClosing(text, i + 1, len, ">") {
                        Some(end) => {
                            let tag = String.substring(text, i, end + 1);
                            processInlineFrom(text, end + 1, len, acc + tag)
                        },
                        None => processInlineFrom(text, i + 1, len, acc + "&lt;")
                    }
                } else
                    processInlineFrom(text, i + 1, len, acc + "&lt;")
            else
                acc + "&lt;"
        else if ch == ">" then
            processInlineFrom(text, i + 1, len, acc + "&gt;")
        else
            processInlineFrom(text, i + 1, len, acc + ch)
    }
}

fn isHtmlTagStart(text: String, i: Int, len: Int): Bool = {
    // Check if < is followed by a letter or / (closing tag)
    if i + 1 >= len then false
    else {
        let next = String.substring(text, i + 1, i + 2);
        next == "/" || next == "a" || next == "b" || next == "c" || next == "d" ||
        next == "e" || next == "f" || next == "g" || next == "h" || next == "i" ||
        next == "j" || next == "k" || next == "l" || next == "m" || next == "n" ||
        next == "o" || next == "p" || next == "q" || next == "r" || next == "s" ||
        next == "t" || next == "u" || next == "v" || next == "w" || next == "x" ||
        next == "y" || next == "z" ||
        next == "A" || next == "B" || next == "C" || next == "D" || next == "E" ||
        next == "F" || next == "G" || next == "H" || next == "I" || next == "J" ||
        next == "K" || next == "L" || next == "M" || next == "N" || next == "O" ||
        next == "P" || next == "Q" || next == "R" || next == "S" || next == "T" ||
        next == "U" || next == "V" || next == "W" || next == "X" || next == "Y" ||
        next == "Z" || next == "!"
    }
}

// Find a closing delimiter starting from position i
fn findClosing(text: String, start: Int, len: Int, delim: String): Option<Int> = {
    let delimLen = String.length(delim);
    findClosingFrom(text, start, len, delim, delimLen)
}

fn findClosingFrom(text: String, i: Int, len: Int, delim: String, delimLen: Int): Option<Int> = {
    if i + delimLen > len then None
    else if String.substring(text, i, i + delimLen) == delim then Some(i)
    else findClosingFrom(text, i + 1, len, delim, delimLen)
}

// === Block-level processing ===

// State for block parser accumulator
// (blocks_html, current_paragraph_lines, in_code_block, code_lang, code_lines, in_blockquote, bq_lines, in_list, list_items, is_ordered)
pub type BlockState =
    | BlockState(String, String, Bool, String, String, Bool, String, Bool, String, Bool)

fn bsHtml(s: BlockState): String = match s { BlockState(h, _, _, _, _, _, _, _, _, _) => h }
fn bsPara(s: BlockState): String = match s { BlockState(_, p, _, _, _, _, _, _, _, _) => p }
fn bsInCode(s: BlockState): Bool = match s { BlockState(_, _, c, _, _, _, _, _, _, _) => c }
fn bsCodeLang(s: BlockState): String = match s { BlockState(_, _, _, l, _, _, _, _, _, _) => l }
fn bsCodeLines(s: BlockState): String = match s { BlockState(_, _, _, _, cl, _, _, _, _, _) => cl }
fn bsInBq(s: BlockState): Bool = match s { BlockState(_, _, _, _, _, bq, _, _, _, _) => bq }
fn bsBqLines(s: BlockState): String = match s { BlockState(_, _, _, _, _, _, bl, _, _, _) => bl }
fn bsInList(s: BlockState): Bool = match s { BlockState(_, _, _, _, _, _, _, il, _, _) => il }
fn bsListItems(s: BlockState): String = match s { BlockState(_, _, _, _, _, _, _, _, li, _) => li }
fn bsOrdered(s: BlockState): Bool = match s { BlockState(_, _, _, _, _, _, _, _, _, o) => o }

// Flush accumulated paragraph
fn flushPara(html: String, para: String): String =
    if para == "" then html
    else html + "<p>" + processInline(String.trim(para)) + "</p>\n"

// Flush accumulated blockquote
fn flushBq(html: String, bqLines: String): String =
    if bqLines == "" then html
    else html + "<blockquote>\n" + convert(bqLines) + "</blockquote>\n"

// Flush accumulated list
fn flushList(html: String, listItems: String, ordered: Bool): String =
    if listItems == "" then html
    else {
        let tag = if ordered then "ol" else "ul";
        html + "<" + tag + ">\n" + listItems + "</" + tag + ">\n"
    }

// Parse markdown blocks from text
pub fn parseBlocks(text: String): String = {
    let lines = String.lines(text);
    let init = BlockState("", "", false, "", "", false, "", false, "", false);
    let final = List.fold(lines, init, fn(state: BlockState, line: String): BlockState => {
        let html = bsHtml(state);
        let para = bsPara(state);
        let inCode = bsInCode(state);
        let codeLang = bsCodeLang(state);
        let codeLines = bsCodeLines(state);
        let inBq = bsInBq(state);
        let bqLines = bsBqLines(state);
        let inList = bsInList(state);
        let listItems = bsListItems(state);
        let ordered = bsOrdered(state);

        // Inside code block
        if inCode then
            if String.startsWith(line, "```") then {
                // End code block
                let codeHtml = if codeLang == "" then
                    "<pre><code>" + codeLines + "</code></pre>\n"
                else
                    "<pre><code class=\"language-" + codeLang + "\">" + codeLines + "</code></pre>\n";
                BlockState(html + codeHtml, "", false, "", "", false, "", false, "", false)
            } else
                BlockState(html, para, true, codeLang, codeLines + escapeHtmlCode(line) + "\n", false, "", false, "", false)
        // Start code block
        else if String.startsWith(line, "```") then {
            let h2 = flushPara(html, para);
            let h3 = flushBq(h2, bqLines);
            let h4 = flushList(h3, listItems, ordered);
            let lang = String.trim(String.substring(line, 3, String.length(line)));
            BlockState(h4, "", true, lang, "", false, "", false, "", false)
        }
        // Blockquote line
        else if String.startsWith(line, "> ") then {
            let h2 = flushPara(html, para);
            let h3 = flushList(h2, listItems, ordered);
            let bqContent = String.substring(line, 2, String.length(line));
            BlockState(h3, "", false, "", "", true, bqLines + bqContent + "\n", false, "", false)
        }
        else if String.trim(line) == ">" then {
            // Empty blockquote continuation
            let h2 = flushPara(html, para);
            let h3 = flushList(h2, listItems, ordered);
            BlockState(h3, "", false, "", "", true, bqLines + "\n", false, "", false)
        }
        // End of blockquote (non-bq line after bq lines)
        else if inBq then {
            let h2 = flushBq(html, bqLines);
            // Re-process this line
            processLine(BlockState(h2, para, false, "", "", false, "", inList, listItems, ordered), line)
        }
        // Unordered list item
        else if String.startsWith(line, "- ") then {
            let h2 = flushPara(html, para);
            let item = String.substring(line, 2, String.length(line));
            BlockState(h2, "", false, "", "", false, "", true, listItems + "<li>" + processInline(item) + "</li>\n", false)
        }
        // Ordered list item (starts with digit + ". ")
        else if isOrderedListItem(line) then {
            let h2 = flushPara(html, para);
            let dotIdx = match String.indexOf(line, ". ") {
                Some(idx) => idx,
                None => 0
            };
            let item = String.substring(line, dotIdx + 2, String.length(line));
            BlockState(h2, "", false, "", "", false, "", true, listItems + "<li>" + processInline(item) + "</li>\n", true)
        }
        else
            processLine(state, line)
    });
    // Flush remaining state
    let h = flushPara(bsHtml(final), bsPara(final));
    let h2 = flushBq(h, bsBqLines(final));
    let h3 = flushList(h2, bsListItems(final), bsOrdered(final));
    h3
}

fn processLine(state: BlockState, line: String): BlockState = {
    let html = bsHtml(state);
    let para = bsPara(state);
    let inList = bsInList(state);
    let listItems = bsListItems(state);
    let ordered = bsOrdered(state);
    let trimmed = String.trim(line);

    // Blank line — flush paragraph and list
    if trimmed == "" then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        BlockState(h3, "", false, "", "", false, "", false, "", false)
    }
    // Heading
    else if String.startsWith(trimmed, "# ") then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        let content = String.substring(trimmed, 2, String.length(trimmed));
        BlockState(h3 + "<h1>" + processInline(content) + "</h1>\n", "", false, "", "", false, "", false, "", false)
    }
    else if String.startsWith(trimmed, "## ") then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        let content = String.substring(trimmed, 3, String.length(trimmed));
        BlockState(h3 + "<h2>" + processInline(content) + "</h2>\n", "", false, "", "", false, "", false, "", false)
    }
    else if String.startsWith(trimmed, "### ") then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        let content = String.substring(trimmed, 4, String.length(trimmed));
        BlockState(h3 + "<h3>" + processInline(content) + "</h3>\n", "", false, "", "", false, "", false, "", false)
    }
    else if String.startsWith(trimmed, "#### ") then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        let content = String.substring(trimmed, 5, String.length(trimmed));
        BlockState(h3 + "<h4>" + processInline(content) + "</h4>\n", "", false, "", "", false, "", false, "", false)
    }
    // Horizontal rule
    else if trimmed == "---" || trimmed == "***" || trimmed == "___" then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        BlockState(h3 + "<hr>\n", "", false, "", "", false, "", false, "", false)
    }
    // Raw HTML line (starts with <)
    else if String.startsWith(trimmed, "<") then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        BlockState(h3 + trimmed + "\n", "", false, "", "", false, "", false, "", false)
    }
    // Image on its own line
    else if String.startsWith(trimmed, "![") then {
        let h2 = flushPara(html, para);
        let h3 = flushList(h2, listItems, ordered);
        BlockState(h3 + "<p>" + processInline(trimmed) + "</p>\n", "", false, "", "", false, "", false, "", false)
    }
    // Continuation of list (indented or sub-item)
    else if inList then
        if String.startsWith(line, "  ") then {
            // Indented content under a list item — append to last item
            BlockState(html, para, false, "", "", false, "", true, listItems, ordered)
        } else {
            // Not a list item — flush list, treat as paragraph
            let h2 = flushList(html, listItems, ordered);
            BlockState(h2, para + trimmed + " ", false, "", "", false, "", false, "", false)
        }
    // Regular text — accumulate into paragraph
    else
        BlockState(html, para + trimmed + " ", false, "", "", false, "", false, "", false)
}

fn isOrderedListItem(line: String): Bool = {
    let trimmed = String.trim(line);
    if String.length(trimmed) < 3 then false
    else {
        let first = String.substring(trimmed, 0, 1);
        let isDigit = first == "0" || first == "1" || first == "2" || first == "3" ||
                      first == "4" || first == "5" || first == "6" || first == "7" ||
                      first == "8" || first == "9";
        if isDigit then String.contains(trimmed, ". ")
        else false
    }
}

// Escape HTML special chars in code blocks (no inline processing)
fn escapeHtmlCode(s: String): String =
    String.replace(String.replace(String.replace(s, "&", "&amp;"), "<", "&lt;"), ">", "&gt;")

// === Main convert function ===

// Convert full markdown text to HTML
pub fn convert(markdown: String): String =
    parseBlocks(markdown)
