
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
                GtkTextIter start_ins = *iter;
                gtk_text_buffer_insert(buffer, iter, inner, -1);
                for (GSList *l = new_tags; l; l = l->next) {
                    gtk_text_buffer_apply_tag(buffer, (GtkTextTag*)l->data, &start_ins, iter);
                }
                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);
                         GtkTextIter start_ins = *iter;
                         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);
            
            GtkTextIter start_ins = *iter;
            gtk_text_buffer_insert(buffer, iter, url, -1);
            
            // 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);
            
            g_free(url);
            p = end; continue;
        }

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