#include <gtk/gtk.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include "md_render.h"

typedef struct {
    char **cells;
    int count;
} TableRow;

static void free_table_row(gpointer data) {
    TableRow *row = (TableRow*)data;
    if (row) {
        for (int i = 0; i < row->count; i++) g_free(row->cells[i]);
        g_free(row->cells);
        g_free(row);
    }
}

static TableRow* parse_table_row(const char *line) {
    char *copy = g_strdup(line);
    char *start = copy;
    while (*start == ' ' || *start == '\t') start++;
    if (*start == '|') start++;
    
    char *end = start + strlen(start) - 1;
    while (end > start && (*end == ' ' || *end == '|' || *end == '\r' || *end == '\n' || *end == '\t')) {
        *end = '\0';
        end--;
    }
    
    char **parts = g_strsplit(start, "|", -1);
    int count = 0;
    while (parts[count]) count++;
    
    TableRow *row = g_malloc0(sizeof(TableRow));
    row->cells = g_malloc0(sizeof(char*) * count);
    row->count = count;
    for (int i = 0; i < count; i++) {
        row->cells[i] = g_strstrip(g_strdup(parts[i]));
    }
    
    g_strfreev(parts);
    g_free(copy);
    return row;
}


static char* process_inline_html(const char *text) {
    if (!text) return g_strdup("");
    GString *s = g_string_new("");
    const char *p = text;
    while (*p) {
        if (strncmp(p, "~~", 2) == 0) {
            const char *end = strstr(p + 2, "~~");
            if (end) {
                char *inner = g_strndup(p + 2, end - p - 2);
                char *processed = process_inline_html(inner);
                g_string_append_printf(s, "<del>%s</del>", processed);
                g_free(inner); g_free(processed);
                p = end + 2; continue;
            }
        }
        if (strncmp(p, "***", 3) == 0) {
            const char *end = strstr(p + 3, "***");
            if (end) {
                char *inner = g_strndup(p + 3, end - p - 3);
                char *processed = process_inline_html(inner);
                g_string_append_printf(s, "<strong><em>%s</em></strong>", processed);
                g_free(inner); g_free(processed);
                p = end + 3; continue;
            }
        }
        if (strncmp(p, "**", 2) == 0 || strncmp(p, "__", 2) == 0) {
            const char *marker = strncmp(p, "**", 2) == 0 ? "**" : "__";
            const char *end = strstr(p + 2, marker);
            if (end) {
                char *inner = g_strndup(p + 2, end - p - 2);
                char *processed = process_inline_html(inner);
                g_string_append_printf(s, "<strong>%s</strong>", processed);
                g_free(inner); g_free(processed);
                p = end + 2; continue;
            }
        }
        if (*p == '*' || *p == '_') {
            char marker[2] = {*p, 0};
            const char *end = strpbrk(p + 1, marker);
            if (end && *end == *p) {
                char *inner = g_strndup(p + 1, end - p - 1);
                char *processed = process_inline_html(inner);
                g_string_append_printf(s, "<em>%s</em>", processed);
                g_free(inner); g_free(processed);
                p = end + 1; continue;
            }
        }
        if (*p == '`') {
            const char *end = strchr(p + 1, '`');
            if (end) {
                char *inner = g_strndup(p + 1, end - p - 1);
                g_string_append_printf(s, "<code>%s</code>", inner);
                p = end + 1; g_free(inner); continue;
            }
        }
        if (strncmp(p, "![", 2) == 0) {
            const char *alt_end = strchr(p + 2, ']');
            if (alt_end && alt_end[1] == '(') {
                const char *url_end = strchr(alt_end + 2, ')');
                if (url_end) {
                    char *alt = g_strndup(p + 2, alt_end - p - 2);
                    char *url = g_strndup(alt_end + 2, url_end - alt_end - 2);
                    g_string_append_printf(s, "<img src=\"%s\" alt=\"%s\" style=\"max-width:100%%;\">", url, alt);
                    g_free(alt); g_free(url);
                    p = url_end + 1; continue;
                }
            }
        }
        if (*p == '[') {
            const char *txt_end = strchr(p + 1, ']');
            if (txt_end && txt_end[1] == '(') {
                const char *url_end = strchr(txt_end + 2, ')');
                if (url_end) {
                    char *txt = g_strndup(p + 1, txt_end - p - 1);
                    char *url = g_strndup(txt_end + 2, url_end - txt_end - 2);
                    char *processed_txt = process_inline_html(txt);
                    g_string_append_printf(s, "<a href=\"%s\">%s</a>", url, processed_txt);
                    g_free(txt); g_free(url); g_free(processed_txt);
                    p = url_end + 1; continue;
                }
            }
        }
        if (strncmp(p, "http://", 7) == 0 || strncmp(p, "https://", 8) == 0) {
            const char *end = p;
            while (*end && !isspace(*end) && *end != ')' && *end != ']' && *end != '>') end++;
            char *url = g_strndup(p, end - p);
            g_string_append_printf(s, "<a href=\"%s\">%s</a>", url, url);
            g_free(url);
            p = end; continue;
        }
        g_string_append_c(s, *p);
        p++;
    }
    return g_string_free(s, FALSE);
}

static void insert_recursive(GtkTextBuffer *buffer, GtkTextIter *iter, const char *text, GSList *tags) {
    if (!text || !*text) return;

    const char *p = text;
    while (*p) {
        // Strikethrough ~~
        if (strncmp(p, "~~", 2) == 0) {
            const char *end = strstr(p + 2, "~~");
            if (end) {
                char *inner = g_strndup(p + 2, end - p - 2);
                GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "strikethrough");
                GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag);
                insert_recursive(buffer, iter, inner, new_tags);
                g_slist_free(new_tags);
                g_free(inner);
                p = end + 2; continue;
            }
        }
        // Bold+Italic ***
        if (strncmp(p, "***", 3) == 0) {
            const char *end = strstr(p + 3, "***");
            if (end) {
                char *inner = g_strndup(p + 3, end - p - 3);
                GtkTextTag *t1 = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "bold");
                GtkTextTag *t2 = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "italic");
                GSList *new_tags = g_slist_prepend(g_slist_prepend(g_slist_copy(tags), t1), t2);
                insert_recursive(buffer, iter, inner, new_tags);
                g_slist_free(new_tags);
                g_free(inner);
                p = end + 3; continue;
            }
        }
        // Bold ** or __
        if (strncmp(p, "**", 2) == 0 || strncmp(p, "__", 2) == 0) {
            const char *marker = strncmp(p, "**", 2) == 0 ? "**" : "__";
            const char *end = strstr(p + 2, marker);
            if (end) {
                char *inner = g_strndup(p + 2, end - p - 2);
                GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "bold");
                GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag);
                insert_recursive(buffer, iter, inner, new_tags);
                g_slist_free(new_tags);
                g_free(inner);
                p = end + 2; continue;
            }
        }
        // Italic * or _
        if (*p == '*' || *p == '_') {
            char marker[2] = {*p, 0};
            const char *end = strpbrk(p + 1, marker);
            if (end && *end == *p) {
                char *inner = g_strndup(p + 1, end - p - 1);
                GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "italic");
                GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag);
                insert_recursive(buffer, iter, inner, new_tags);
                g_slist_free(new_tags);
                g_free(inner);
                p = end + 1; continue;
            }
        }
        // Code `
        if (*p == '`') {
            const char *end = strchr(p + 1, '`');
            if (end) {
                char *inner = g_strndup(p + 1, end - p - 1);
                GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "code");
                GSList *new_tags = g_slist_prepend(g_slist_copy(tags), tag);
                // Code is not recursive - use marks to preserve position
                GtkTextMark *start_mark = gtk_text_buffer_create_mark(buffer, NULL, iter, TRUE);
                gtk_text_buffer_insert(buffer, iter, inner, -1);
                GtkTextIter start_ins;
                gtk_text_buffer_get_iter_at_mark(buffer, &start_ins, start_mark);
                for (GSList *l = new_tags; l; l = l->next) {
                    gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter);
                }
                gtk_text_buffer_delete_mark(buffer, start_mark);
                g_slist_free(new_tags);
                g_free(inner);
                p = end + 1; continue;
            }
        }
        // Image ![alt](url)
        if (strncmp(p, "![", 2) == 0) {
             const char *alt_end = strchr(p + 2, ']');
             if (alt_end && alt_end[1] == '(') {
                 const char *url_end = strchr(alt_end + 2, ')');
                 if (url_end) {
                     char *path_start = (char*)alt_end + 2;
                     char *path = g_strndup(path_start, url_end - path_start);
                     if (strncmp(path, "http", 4) == 0) {
                         // Placeholder for remote
                         char *msg = g_strdup_printf("[Remote Image: %s]", path);
                         gtk_text_buffer_insert(buffer, iter, msg, -1);
                         g_free(msg);
                     } else {
                         GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(path, 600, -1, TRUE, NULL);
                         if (pixbuf) {
                             gtk_text_buffer_insert_pixbuf(buffer, iter, pixbuf);
                             g_object_unref(pixbuf);
                         } else {
                             char *msg = g_strdup_printf("[Image not found: %s]", path);
                             gtk_text_buffer_insert(buffer, iter, msg, -1);
                             g_free(msg);
                         }
                     }
                     g_free(path);
                     p = url_end + 1; continue;
                 }
             }
        }
        // Link [text](url)
        if (*p == '[') {
            const char *txt_end = strchr(p + 1, ']');
            if (txt_end && txt_end[1] == '(') {
                const char *url_end = strchr(txt_end + 2, ')');
                if (url_end) {
                    char *txt = g_strndup(p + 1, txt_end - p - 1);
                    char *url = g_strndup(txt_end + 2, url_end - txt_end - 2);
                    
                    GtkTextTag *url_tag = gtk_text_buffer_create_tag(buffer, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
                    g_object_set_data_full(G_OBJECT(url_tag), "url", g_strdup(url), g_free);
                    
                    GSList *new_tags = g_slist_prepend(g_slist_copy(tags), url_tag);
                    insert_recursive(buffer, iter, txt, new_tags);
                    
                    g_slist_free(new_tags);
                    g_free(txt); g_free(url);
                    p = url_end + 1; continue;
                }
            }
        }
        // Auto-link http://...
        if (strncmp(p, "http://", 7) == 0 || strncmp(p, "https://", 8) == 0) {
            const char *end = p;
            while (*end && !isspace(*end) && *end != ')' && *end != ']' && *end != '>') end++;
            char *url = g_strndup(p, end - p);
            
            GtkTextTag *url_tag = gtk_text_buffer_create_tag(buffer, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
            g_object_set_data_full(G_OBJECT(url_tag), "url", g_strdup(url), g_free);
            
            GtkTextMark *start_mark = gtk_text_buffer_create_mark(buffer, NULL, iter, TRUE);
            gtk_text_buffer_insert(buffer, iter, url, -1);
            GtkTextIter start_ins;
            gtk_text_buffer_get_iter_at_mark(buffer, &start_ins, start_mark);
            
            // Apply background tags + url tag
            for (GSList *l = tags; l; l = l->next) gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter);
            gtk_text_buffer_apply_tag(buffer, url_tag, &start_ins, iter);
            
            gtk_text_buffer_delete_mark(buffer, start_mark);  
            g_free(url);
            p = end; continue;
        }

        // Plain text
        GtkTextMark *start_mark = gtk_text_buffer_create_mark(buffer, NULL, iter, TRUE);
        char buf[2] = {*p, 0};
        gtk_text_buffer_insert(buffer, iter, buf, 1);
        GtkTextIter start_ins;
        gtk_text_buffer_get_iter_at_mark(buffer, &start_ins, start_mark);
        for (GSList *l = tags; l; l = l->next) {
            gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter);
        }
        gtk_text_buffer_delete_mark(buffer, start_mark);
        p++;
    }
}

void md_render_init_tags(GtkTextBuffer *buffer) {
    gtk_text_buffer_create_tag(buffer, "h1", "weight", PANGO_WEIGHT_BOLD, "size", 24 * PANGO_SCALE, NULL);
    gtk_text_buffer_create_tag(buffer, "h2", "weight", PANGO_WEIGHT_BOLD, "size", 20 * PANGO_SCALE, NULL);
    gtk_text_buffer_create_tag(buffer, "h3", "weight", PANGO_WEIGHT_BOLD, "size", 16 * PANGO_SCALE, NULL);
    gtk_text_buffer_create_tag(buffer, "h4", "weight", PANGO_WEIGHT_BOLD, "size", 14 * PANGO_SCALE, NULL);
    gtk_text_buffer_create_tag(buffer, "h5", "weight", PANGO_WEIGHT_BOLD, "size", 12 * PANGO_SCALE, NULL);
    gtk_text_buffer_create_tag(buffer, "h6", "weight", PANGO_WEIGHT_BOLD, "size", 10 * PANGO_SCALE, NULL);
    gtk_text_buffer_create_tag(buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL);
    gtk_text_buffer_create_tag(buffer, "italic", "style", PANGO_STYLE_ITALIC, NULL);
    gtk_text_buffer_create_tag(buffer, "bold_italic", "weight", PANGO_WEIGHT_BOLD, "style", PANGO_STYLE_ITALIC, NULL);
    gtk_text_buffer_create_tag(buffer, "code", "family", "monospace", NULL);
    gtk_text_buffer_create_tag(buffer, "checkbox_off", "foreground", "red", "weight", PANGO_WEIGHT_BOLD, NULL);
    gtk_text_buffer_create_tag(buffer, "checkbox_on", "foreground", "green", "weight", PANGO_WEIGHT_BOLD, NULL);
    gtk_text_buffer_create_tag(buffer, "code_block", "family", "monospace", 
                               "pixels-above-lines", 8, "pixels-below-lines", 8, 
                               "left-margin", 15, "right-margin", 15, NULL);
    gtk_text_buffer_create_tag(buffer, "list", "left-margin", 20, NULL);
    gtk_text_buffer_create_tag(buffer, "blockquote", "left-margin", 30, "style", PANGO_STYLE_ITALIC, NULL);
    gtk_text_buffer_create_tag(buffer, "hr", "underline", PANGO_UNDERLINE_SINGLE, "pixels-above-lines", 10, "pixels-below-lines", 10, NULL);
    gtk_text_buffer_create_tag(buffer, "table", "family", "monospace", "left-margin", 10, NULL);
    gtk_text_buffer_create_tag(buffer, "link", "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL);
    gtk_text_buffer_create_tag(buffer, "strikethrough", "strikethrough", TRUE, NULL);
    gtk_text_buffer_create_tag(buffer, "normal_text", NULL);
}

static void update_tag_colors(GtkTextBuffer *buffer, int theme) {
    const char *h1_fg = theme ? "#ffffff" : "#1a1a1a";
    const char *h2_fg = theme ? "#f0f0f0" : "#2d2d2d";
    const char *h3_fg = theme ? "#e0e0e0" : "#444444";
    const char *h4_fg = theme ? "#cccccc" : "#666666";
    const char *h5_fg = theme ? "#bbbbbb" : "#777777";
    const char *h6_fg = theme ? "#aaaaaa" : "#888888";
    const char *text_fg = theme ? "#ffffff" : "#24292e";
    const char *bold_fg = theme ? "#ffffff" : "#000000";
    const char *italic_fg = theme ? "#cccccc" : "#555555";
    const char *bq_fg = theme ? "#95a5a6" : "#7f8c8d";
    const char *code_bg = theme ? "#2d3436" : "#f0f0f0";
    const char *code_fg = theme ? "#fab1a0" : "#d73a49";
    const char *cb_bg = theme ? "#2d3436" : "#f6f8fa";
    const char *cb_fg = theme ? "#dfe6e9" : "#24292e";
    const char *cb_on_fg = theme ? "#55efc4" : "#27ae60";
    const char *cb_off_fg = theme ? "#ff7675" : "#d63031";
    const char *link_fg = theme ? "#a5d6ff" : "#0984e3";
    const char *hr_fg = theme ? "#636e72" : "#dfe6e9";

    GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer);
    
    g_object_set(gtk_text_tag_table_lookup(table, "checkbox_on"), "foreground", cb_on_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "checkbox_off"), "foreground", cb_off_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "h1"), "foreground", h1_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "h2"), "foreground", h2_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "h3"), "foreground", h3_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "h4"), "foreground", h4_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "h5"), "foreground", h5_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "h6"), "foreground", h6_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "bold"), "foreground", bold_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "italic"), "foreground", italic_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "bold_italic"), "foreground", bold_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "blockquote"), "foreground", bq_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "code"), "background", code_bg, "foreground", code_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "code_block"), "background", cb_bg, "foreground", cb_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "list"), "foreground", text_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "link"), "foreground", link_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "hr"), "foreground", hr_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "table"), "foreground", text_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "strikethrough"), "foreground", italic_fg, NULL);
    g_object_set(gtk_text_tag_table_lookup(table, "normal_text"), "foreground", text_fg, NULL);
}

void md_render_to_buffer(GtkTextBuffer *buffer, const char *text, int theme) {
    update_tag_colors(buffer, theme);
    gtk_text_buffer_set_text(buffer, "", 0);

    GtkTextIter iter;
    gtk_text_buffer_get_start_iter(buffer, &iter);

    char *line_copy = g_strdup(text);
    char *saveptr;
    char *token = strtok_r(line_copy, "\n", &saveptr);
    char *pending_token = NULL;
    int in_code_block = 0;

    while (token != NULL || pending_token != NULL) {
        if (pending_token) {
            token = pending_token;
            pending_token = NULL;
        }

        char *p_trimmed = token;
        while (*p_trimmed == ' ' || *p_trimmed == '\t') p_trimmed++;

        if (strncmp(p_trimmed, "```", 3) == 0) {
            in_code_block = !in_code_block;
            token = strtok_r(NULL, "\n", &saveptr);
            continue;
        }

        if (in_code_block) {
            gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, token, -1, "code_block", NULL);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (p_trimmed[0] == '|') {
            GList *rows = NULL;
            int max_cols = 0;
            char *current_line = token;
            
            while (current_line) {
                char *cl_trimmed = current_line;
                while (*cl_trimmed == ' ' || *cl_trimmed == '\t') cl_trimmed++;
                if (cl_trimmed[0] == '|') {
                    TableRow *row = parse_table_row(current_line);
                    rows = g_list_append(rows, row);
                    if (row->count > max_cols) max_cols = row->count;
                    current_line = strtok_r(NULL, "\n", &saveptr);
                } else {
                    pending_token = current_line;
                    break;
                }
            }

            if (rows) {
                int *col_widths = g_malloc0(sizeof(int) * max_cols);
                for (GList *l = rows; l; l = l->next) {
                    TableRow *row = (TableRow*)l->data;
                    for (int i = 0; i < row->count; i++) {
                        int len = g_utf8_strlen(row->cells[i], -1);
                        if (len > col_widths[i]) col_widths[i] = len;
                    }
                }

                for (GList *l = rows; l; l = l->next) {
                    TableRow *row = (TableRow*)l->data;
                    if (row->count > 0 && strstr(row->cells[0], "---")) continue; // Skip divider row in visual view

                    gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "| ", 2, "table", NULL);
                    for (int i = 0; i < max_cols; i++) {
                        const char *cell_text = (i < row->count) ? row->cells[i] : "";
                        gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, cell_text, -1, "table", NULL);
                        int padding = col_widths[i] - g_utf8_strlen(cell_text, -1);
                        for (int k = 0; k < padding; k++) gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, " ", 1, "table", NULL);
                        if (i < max_cols - 1) gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, " | ", 3, "table", NULL);
                    }
                    gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, " |", 2, "table", NULL);
                    gtk_text_buffer_insert(buffer, &iter, "\n", 1);
                }
                g_free(col_widths);
                g_list_free_full(rows, free_table_row);
            }
            if (pending_token) continue;
        } else if (strncmp(p_trimmed, "---", 3) == 0 || strncmp(p_trimmed, "***", 3) == 0 || strncmp(p_trimmed, "___", 3) == 0) {
            gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "                                                                                ", -1, "hr", NULL);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "###### ", 7) == 0) {
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "h6");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, p_trimmed + 7, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "##### ", 6) == 0) {
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "h5");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, p_trimmed + 6, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "#### ", 5) == 0) {
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "h4");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, p_trimmed + 5, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "### ", 4) == 0) {
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "h3");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, p_trimmed + 4, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "## ", 3) == 0) {
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "h2");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, p_trimmed + 3, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "# ", 2) == 0) {
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "h1");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, p_trimmed + 2, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (p_trimmed[0] == '>') {
            int bq_level = 0;
            char *p = p_trimmed;
            while (*p == '>' || *p == ' ') {
                if (*p == '>') bq_level++;
                p++;
            }
            char tag[32];
            snprintf(tag, sizeof(tag), "blockquote_%d", bq_level > 5 ? 5 : bq_level);
            if (!gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag)) {
                gtk_text_buffer_create_tag(buffer, tag, "left-margin", bq_level * 30, "style", PANGO_STYLE_ITALIC, NULL);
            }
            GtkTextTag *bq_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag);
            GtkTextTag *base_bq = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "blockquote");
            GSList *tags = g_slist_prepend(g_slist_prepend(NULL, bq_tag), base_bq);
            insert_recursive(buffer, &iter, p, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "- [ ]", 5) == 0 && (p_trimmed[5] == ' ' || p_trimmed[5] == '\0')) {
            int indent = p_trimmed - token;
            char tag[32];
            snprintf(tag, sizeof(tag), "list_%d", indent);
            if (!gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag)) {
                gtk_text_buffer_create_tag(buffer, tag, "left-margin", 20 + indent * 10, NULL);
            }
            GtkTextTag *list_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag);
            GtkTextTag *cb_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "checkbox_off");
            // Checkbox part
            gtk_text_buffer_insert_with_tags(buffer, &iter, "☐ ", -1, cb_tag, list_tag, NULL);
            // Text part
            GSList *tags = g_slist_prepend(NULL, list_tag);
            insert_recursive(buffer, &iter, p_trimmed + (p_trimmed[5] == ' ' ? 6 : 5), tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if ((strncmp(p_trimmed, "- [x]", 5) == 0 || strncmp(p_trimmed, "- [X]", 5) == 0) && (p_trimmed[5] == ' ' || p_trimmed[5] == '\0')) {
            int indent = p_trimmed - token;
            char tag[32];
            snprintf(tag, sizeof(tag), "list_%d", indent);
            if (!gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag)) {
                gtk_text_buffer_create_tag(buffer, tag, "left-margin", 20 + indent * 10, NULL);
            }
            GtkTextTag *list_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag);
            GtkTextTag *cb_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "checkbox_on");
            // Checkbox part
            gtk_text_buffer_insert_with_tags(buffer, &iter, "☑ ", -1, cb_tag, list_tag, NULL);
            // Text part
            GSList *tags = g_slist_prepend(NULL, list_tag);
            insert_recursive(buffer, &iter, p_trimmed + (p_trimmed[5] == ' ' ? 6 : 5), tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (strncmp(p_trimmed, "- ", 2) == 0 || strncmp(p_trimmed, "* ", 2) == 0) {
            int indent = p_trimmed - token;
            char tag[32];
            snprintf(tag, sizeof(tag), "list_%d", indent);
            if (!gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag)) {
                gtk_text_buffer_create_tag(buffer, tag, "left-margin", 20 + indent * 10, NULL);
            }
            GtkTextTag *list_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag);
            GtkTextTag *base_list = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "list");
            // Bullet part
            gtk_text_buffer_insert_with_tags(buffer, &iter, "• ", -1, list_tag, base_list, NULL);
            // Text part
            GSList *tags = g_slist_prepend(g_slist_prepend(NULL, list_tag), base_list);
            insert_recursive(buffer, &iter, p_trimmed + 2, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else if (isdigit(p_trimmed[0]) && strstr(p_trimmed, ". ")) {
             char *p = strstr(p_trimmed, ". ");
             if (p - p_trimmed < 4) {
                 int indent = p_trimmed - token;
                 char tag[32];
                 snprintf(tag, sizeof(tag), "list_%d", indent);
                 if (!gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag)) {
                     gtk_text_buffer_create_tag(buffer, tag, "left-margin", 20 + indent * 10, NULL);
                 }
                 GtkTextTag *list_tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tag);
                 GtkTextTag *base_list = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "list");
                 GSList *tags = g_slist_prepend(g_slist_prepend(NULL, list_tag), base_list);
                 insert_recursive(buffer, &iter, p_trimmed, tags);
                 g_slist_free(tags);
                 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
             } else {
                 goto normal_text;
             }
        } else if (strncmp(token, "![", 2) == 0) {
            insert_recursive(buffer, &iter, token, NULL);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        } else {
            normal_text: ;
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "normal_text");
            GSList *tags = g_slist_prepend(NULL, tag);
            insert_recursive(buffer, &iter, token, tags);
            g_slist_free(tags);
            gtk_text_buffer_insert(buffer, &iter, "\n", 1);
        }
        token = strtok_r(NULL, "\n", &saveptr);
    }
    free(line_copy);
}

char* md_to_html(const char *text) {
    GString *html = g_string_new("<!DOCTYPE html><html><head><meta charset=\"UTF-8\">");
    g_string_append(html, "<style>body{font-family:sans-serif;line-height:1.6;max-width:800px;margin:2em auto;padding:0 1em;color:#24292e;}");
    g_string_append(html, "h1,h2,h3,h4,h5,h6{border-bottom:1px solid #eaecef;padding-bottom:.3em;}");
    g_string_append(html, "pre{background:#f6f8fa;padding:16px;border-radius:3px;overflow:auto;}");
    g_string_append(html, "code{background:rgba(27,31,35,0.05);padding:.2em .4em;border-radius:3px;font-family:monospace;}");
    g_string_append(html, "blockquote{border-left:.25em solid #dfe2e5;color:#6a737d;padding:0 1em;margin:0 0 16px 0;}");
    g_string_append(html, "table{border-collapse:collapse;width:100%;}table td,table th{border:1px solid #dfe2e5;padding:6px 13px;}");
    g_string_append(html, "hr{height:.25em;background-color:#e1e4e8;border:0;margin:24px 0;}</style></head><body>");

    char *line_copy = g_strdup(text);
    char *saveptr;
    char *token = strtok_r(line_copy, "\n", &saveptr);
    char *pending_token = NULL;
    int in_code_block = 0;

    while (token != NULL || pending_token != NULL) {
        if (pending_token) {
            token = pending_token;
            pending_token = NULL;
        }

        char *p_trimmed = token;
        while (*p_trimmed == ' ' || *p_trimmed == '\t') p_trimmed++;

        if (strncmp(p_trimmed, "```", 3) == 0) {
            if (in_code_block) g_string_append(html, "</pre>\n");
            else g_string_append(html, "<pre>\n");
            in_code_block = !in_code_block;
            token = strtok_r(NULL, "\n", &saveptr);
            continue;
        }

        if (in_code_block) {
            g_string_append(html, token);
            g_string_append(html, "\n");
        } else if (strncmp(token, "---", 3) == 0 || strncmp(token, "***", 3) == 0) {
            g_string_append(html, "<hr>\n");
        } else if (strncmp(token, "###### ", 7) == 0) {
            char *inline_text = process_inline_html(token + 7);
            g_string_append_printf(html, "<h6>%s</h6>\n", inline_text);
            g_free(inline_text);
        } else if (strncmp(token, "##### ", 6) == 0) {
            char *inline_text = process_inline_html(token + 6);
            g_string_append_printf(html, "<h5>%s</h5>", inline_text);
            g_free(inline_text);
        } else if (strncmp(token, "#### ", 5) == 0) {
            char *inline_text = process_inline_html(token + 5);
            g_string_append_printf(html, "<h4>%s</h4>", inline_text);
            g_free(inline_text);
        } else if (strncmp(token, "### ", 4) == 0) {
            char *inline_text = process_inline_html(token + 4);
            g_string_append_printf(html, "<h3>%s</h3>", inline_text);
            g_free(inline_text);
        } else if (strncmp(token, "## ", 3) == 0) {
            char *inline_text = process_inline_html(token + 3);
            g_string_append_printf(html, "<h2>%s</h2>", inline_text);
            g_free(inline_text);
        } else if (strncmp(token, "# ", 2) == 0) {
            char *inline_text = process_inline_html(token + 2);
            g_string_append_printf(html, "<h1>%s</h1>\n", inline_text);
            g_free(inline_text);
        } else if (p_trimmed[0] == '>') {
            int bq_level = 0;
            char *p = p_trimmed;
            while (*p == '>' || *p == ' ') {
                if (*p == '>') bq_level++;
                p++;
            }
            char *inline_text = process_inline_html(p);
            for (int i = 0; i < bq_level; i++) g_string_append(html, "<blockquote>");
            g_string_append_printf(html, "%s", inline_text);
            for (int i = 0; i < bq_level; i++) g_string_append(html, "</blockquote>\n");
            g_free(inline_text);
        } else if (strncmp(p_trimmed, "- [ ]", 5) == 0 && (p_trimmed[5] == ' ' || p_trimmed[5] == '\0')) {
            char *inline_text = process_inline_html(p_trimmed + (p_trimmed[5] == ' ' ? 6 : 5));
            g_string_append_printf(html, "\t<li><input type=\"checkbox\" disabled> %s</li>\n", inline_text);
            g_free(inline_text);
        } else if ((strncmp(p_trimmed, "- [x]", 5) == 0 || strncmp(p_trimmed, "- [X]", 5) == 0) && (p_trimmed[5] == ' ' || p_trimmed[5] == '\0')) {
            char *inline_text = process_inline_html(p_trimmed + (p_trimmed[5] == ' ' ? 6 : 5));
            g_string_append_printf(html, "\t<li><input type=\"checkbox\" checked disabled> %s</li>\n", inline_text);
            g_free(inline_text);
        } else if (strncmp(p_trimmed, "- ", 2) == 0 || strncmp(p_trimmed, "* ", 2) == 0) {
            int indent = p_trimmed - token;
            char *inline_text = process_inline_html(p_trimmed + 2);
            g_string_append_printf(html, "\t<li style=\"margin-left: %dpx\">%s</li>\n", indent * 20, inline_text);
            g_free(inline_text);
        } else if (p_trimmed[0] == '!' && p_trimmed[1] == '[') {
            char *alt_start = p_trimmed + 2;
            char *alt_end = strchr(alt_start, ']');
            if (alt_end && alt_end[1] == '(') {
                char *url_start = alt_end + 2;
                char *url_end = strchr(url_start, ')');
                if (url_end) {
                    *alt_end = '\0';
                    *url_end = '\0';
                    g_string_append_printf(html, "<img src=\"%s\" alt=\"%s\" style=\"max-width:100%%;height:auto;\">\n", url_start, alt_start);
                    *alt_end = ']';
                    *url_end = ')';
                }
            }
        } else if (token[0] == '|') {
            g_string_append(html, "<table>");
            int first_row = 1;
            char *current_line = token;
            while (current_line) {
                if (current_line[0] == '|') {
                    TableRow *row = parse_table_row(current_line);
                    if (row->count > 0 && strstr(row->cells[0], "---")) {
                        free_table_row(row);
                    } else {
                        g_string_append(html, "<tr>");
                        for (int i = 0; i < row->count; i++) {
                            const char *tag = first_row ? "th" : "td";
                            g_string_append_printf(html, "<%s>%s</%s>", tag, row->cells[i], tag);
                        }
                        g_string_append(html, "</tr>");
                        first_row = 0;
                        free_table_row(row);
                    }
                    current_line = strtok_r(NULL, "\n", &saveptr);
                } else {
                    pending_token = current_line;
                    break;
                }
            }
            g_string_append(html, "</table>");
            if (pending_token) continue;
        } else {
            char *inline_text = process_inline_html(token);
            g_string_append_printf(html, "<p>%s</p>", inline_text);
            g_free(inline_text);
        }
        if (!pending_token)
            token = strtok_r(NULL, "\n", &saveptr);
    }

    if (in_code_block) g_string_append(html, "</pre>\n");
    g_string_append(html, "</body>\n</html>");
    free(line_copy);
    return g_string_free(html, FALSE);
}

void md_render_highlight_editor(GtkTextBuffer *buffer, int theme) {
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer, &start, &end);
    
    const char *tags[] = {"h1", "h2", "h3", "h4", "h5", "h6", "bold", "italic", "bold_italic", "code", "code_block", "list", "blockquote", "checkbox_on", "checkbox_off", "normal_text"};
    for (int i = 0; i < 16; i++) {
        gtk_text_buffer_remove_tag_by_name(buffer, tags[i], &start, &end);
    }

    update_tag_colors(buffer, theme);

    int line_count = gtk_text_buffer_get_line_count(buffer);
    for (int i = 0; i < line_count; i++) {
        GtkTextIter line_start, line_end;
        gtk_text_buffer_get_iter_at_line(buffer, &line_start, i);
        line_end = line_start;
        gtk_text_iter_forward_to_line_end(&line_end);
        
        char *line_text = gtk_text_buffer_get_text(buffer, &line_start, &line_end, FALSE);
        if (!line_text) continue;

        char *p_trimmed = line_text;
        while (*p_trimmed == ' ' || *p_trimmed == '\t') p_trimmed++;

        if (strncmp(p_trimmed, "###### ", 7) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "h6", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "##### ", 6) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "h5", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "#### ", 5) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "h4", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "### ", 4) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "h3", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "## ", 3) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "h2", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "# ", 2) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "h1", &line_start, &line_end);
        } else if (strncmp(p_trimmed, ">> ", 3) == 0 || strncmp(p_trimmed, "> ", 2) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "blockquote", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "- [ ]", 5) == 0 && (p_trimmed[5] == ' ' || p_trimmed[5] == '\0')) {
            gtk_text_buffer_apply_tag_by_name(buffer, "checkbox_off", &line_start, &line_end);
        } else if ((strncmp(p_trimmed, "- [x]", 5) == 0 || strncmp(p_trimmed, "- [X]", 5) == 0) && (p_trimmed[5] == ' ' || p_trimmed[5] == '\0')) {
            gtk_text_buffer_apply_tag_by_name(buffer, "checkbox_on", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "- ", 2) == 0 || strncmp(p_trimmed, "* ", 2) == 0 || isdigit(p_trimmed[0])) {
            gtk_text_buffer_apply_tag_by_name(buffer, "list", &line_start, &line_end);
        } else if (strncmp(p_trimmed, "```", 3) == 0) {
            gtk_text_buffer_apply_tag_by_name(buffer, "code_block", &line_start, &line_end);
        } else {
            const char *p = line_text;
            while (*p) {
                if (strncmp(p, "~~", 2) == 0) {
                    const char *end_p = strstr(p + 2, "~~");
                    if (end_p) {
                         GtkTextIter match_start = line_start;
                         GtkTextIter match_end = line_start;
                         gtk_text_iter_forward_chars(&match_start, p - line_text);
                         gtk_text_iter_forward_chars(&match_end, end_p + 2 - line_text);
                         gtk_text_buffer_apply_tag_by_name(buffer, "strikethrough", &match_start, &match_end);
                         p = end_p + 1;
                    }
                }
                if (strncmp(p, "**", 2) == 0) {
                    const char *end_p = strstr(p + 2, "**");
                    if (end_p) {
                         GtkTextIter match_start = line_start;
                         GtkTextIter match_end = line_start;
                         gtk_text_iter_forward_chars(&match_start, p - line_text);
                         gtk_text_iter_forward_chars(&match_end, end_p + 2 - line_text);
                         gtk_text_buffer_apply_tag_by_name(buffer, "bold", &match_start, &match_end);
                         p = end_p + 1;
                    }
                } else if (*p == '*' || *p == '_') {
                     const char *end_p = strpbrk(p + 1, "*_");
                     if (end_p && *end_p == *p) {
                         GtkTextIter match_start = line_start;
                         GtkTextIter match_end = line_start;
                         gtk_text_iter_forward_chars(&match_start, p - line_text);
                         gtk_text_iter_forward_chars(&match_end, end_p + 1 - line_text);
                         gtk_text_buffer_apply_tag_by_name(buffer, "italic", &match_start, &match_end);
                         p = end_p;
                     }
                } else if (*p == '`') {
                    const char *end_p = strchr(p + 1, '`');
                    if (end_p) {
                         GtkTextIter match_start = line_start;
                         GtkTextIter match_end = line_start;
                         gtk_text_iter_forward_chars(&match_start, p - line_text);
                         gtk_text_iter_forward_chars(&match_end, end_p + 1 - line_text);
                         gtk_text_buffer_apply_tag_by_name(buffer, "code", &match_start, &match_end);
                         p = end_p;
                    }
                }
                p++;
            }
        }
        g_free(line_text);
    }
}

GList* md_get_headers(const char *text) {
    GList *headers = NULL;
    char *line_copy = strdup(text);
    char *saveptr;
    char *token = strtok_r(line_copy, "\n", &saveptr);
    int line_num = 0;

    while (token != NULL) {
        int level = 0;
        if (strncmp(token, "###### ", 7) == 0) level = 6;
        else if (strncmp(token, "##### ", 6) == 0) level = 5;
        else if (strncmp(token, "#### ", 5) == 0) level = 4;
        else if (strncmp(token, "### ", 4) == 0) level = 3;
        else if (strncmp(token, "## ", 3) == 0) level = 2;
        else if (strncmp(token, "# ", 2) == 0) level = 1;

        if (level > 0) {
            MdHeader *h = g_malloc0(sizeof(MdHeader));
            h->text = g_strdup(token + level + 1);
            h->line = line_num;
            h->level = level;
            headers = g_list_append(headers, h);
        }
        token = strtok_r(NULL, "\n", &saveptr);
        line_num++;
    }
    free(line_copy);
    return headers;
}

void md_free_headers(GList *headers) {
    if (!headers) return;
    for (GList *l = headers; l != NULL; l = l->next) {
        MdHeader *h = (MdHeader*)l->data;
        g_free(h->text);
        g_free(h);
    }
    g_list_free(headers);
}
