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

GtkWidget *text_view_editor;
GtkWidget *text_view_viewer;
GtkTextBuffer *buffer_editor;
GtkTextBuffer *buffer_viewer;
void update_line_numbers();

GtkWidget *statusbar;
GtkWidget *toc_sidebar;
GtkListStore *toc_store;
char *current_filename = NULL;
int dark_mode = 1;
int line_wrap = 1;
int auto_save_enabled = 0;
int show_toc = 1;
GList *recent_files = NULL;
guint status_context_id;
GtkWidget *line_num_view;
GtkTextBuffer *buffer_line_nums;
GtkWidget *find_dialog = NULL;
GtkWidget *find_entry, *replace_entry;
guint debounce_id = 0;

typedef struct {
    GList *undo_stack;
    GList *redo_stack;
    int max_depth;
} UndoManager;

UndoManager *undo_manager;

void undo_push(const char *text);
void undo_perform();
void redo_perform();
void undo_free();

void update_preview();
static void on_editor_changed(GtkTextBuffer *buffer, gpointer user_data);

void load_config();
void save_config();
void add_recent_file(const char *filename);
void update_recent_menu();
GtkWidget *recent_menu;

enum {
    COL_TEXT,
    COL_LINE,
    NUM_COLS
};

void update_ui_colors() {
    GdkColor bg, fg;
    if (dark_mode) {
        gdk_color_parse("#1e1e1e", &bg);
        gdk_color_parse("#ffffff", &fg);
    } else {
        gdk_color_parse("#ffffff", &bg);
        gdk_color_parse("#24292e", &fg);
    }
    gtk_widget_modify_base(text_view_editor, GTK_STATE_NORMAL, &bg);
    gtk_widget_modify_text(text_view_editor, GTK_STATE_NORMAL, &fg);
    gtk_widget_modify_base(text_view_viewer, GTK_STATE_NORMAL, &bg);
    gtk_widget_modify_text(text_view_viewer, GTK_STATE_NORMAL, &fg);
}

void save_config() {
    char *config_dir = g_build_filename(g_get_user_config_dir(), "mdviewer", NULL);
    g_mkdir_with_parents(config_dir, 0755);
    char *config_path = g_build_filename(config_dir, "config", NULL);
    FILE *f = fopen(config_path, "w");
    if (f) {
        fprintf(f, "dark_mode=%d\n", dark_mode);
        fprintf(f, "line_wrap=%d\n", line_wrap);
        fprintf(f, "auto_save=%d\n", auto_save_enabled);
        fprintf(f, "show_toc=%d\n", show_toc);
        fclose(f);
    }
    
    char *recent_path = g_build_filename(config_dir, "recent", NULL);
    f = fopen(recent_path, "w");
    if (f) {
        for (GList *l = recent_files; l; l = l->next) {
            fprintf(f, "%s\n", (char*)l->data);
        }
        fclose(f);
    }
    g_free(config_path);
    g_free(recent_path);
    g_free(config_dir);
}

void load_config() {
    char *config_path = g_build_filename(g_get_user_config_dir(), "mdviewer", "config", NULL);
    FILE *f = fopen(config_path, "r");
    if (f) {
        char line[128];
        while (fgets(line, sizeof(line), f)) {
            if (strncmp(line, "dark_mode=", 10) == 0) dark_mode = atoi(line + 10);
            else if (strncmp(line, "line_wrap=", 10) == 0) line_wrap = atoi(line + 10);
            else if (strncmp(line, "auto_save=", 10) == 0) auto_save_enabled = atoi(line + 10);
            else if (strncmp(line, "show_toc=", 9) == 0) show_toc = atoi(line + 9);
        }
        fclose(f);
    }
    g_free(config_path);

    char *recent_path = g_build_filename(g_get_user_config_dir(), "mdviewer", "recent", NULL);
    f = fopen(recent_path, "r");
    if (f) {
        char line[1024];
        while (fgets(line, sizeof(line), f)) {
            line[strcspn(line, "\n")] = 0;
            if (strlen(line) > 0)
                recent_files = g_list_append(recent_files, g_strdup(line));
        }
        fclose(f);
    }
    g_free(recent_path);
}

void load_file(const char *filename);

static void on_recent_activate(GtkMenuItem *item, gpointer user_data) {
    load_file((char*)user_data);
}

void update_recent_menu() {
    if (!recent_menu) return;
    GList *children = gtk_container_get_children(GTK_CONTAINER(recent_menu));
    for (GList *l = children; l; l = l->next) gtk_widget_destroy(GTK_WIDGET(l->data));
    g_list_free(children);

    for (GList *l = recent_files; l; l = l->next) {
        GtkWidget *item = gtk_menu_item_new_with_label((char*)l->data);
        g_signal_connect(item, "activate", G_CALLBACK(on_recent_activate), l->data);
        gtk_menu_shell_append(GTK_MENU_SHELL(recent_menu), item);
    }
    gtk_widget_show_all(recent_menu);
}

void undo_push(const char *text) {
    if (undo_manager->undo_stack && strcmp((char*)undo_manager->undo_stack->data, text) == 0) return;
    
    undo_manager->undo_stack = g_list_prepend(undo_manager->undo_stack, g_strdup(text));
    if (g_list_length(undo_manager->undo_stack) > (guint)undo_manager->max_depth) {
        GList *last = g_list_last(undo_manager->undo_stack);
        g_free(last->data);
        undo_manager->undo_stack = g_list_remove_link(undo_manager->undo_stack, last);
        g_list_free_1(last);
    }
    
    // Clear redo stack on new change
    g_list_free_full(undo_manager->redo_stack, g_free);
    undo_manager->redo_stack = NULL;
}

void undo_perform() {
    if (!undo_manager->undo_stack || !undo_manager->undo_stack->next) return;
    
    // Current state to redo
    undo_manager->redo_stack = g_list_prepend(undo_manager->redo_stack, undo_manager->undo_stack->data);
    undo_manager->undo_stack = g_list_remove_link(undo_manager->undo_stack, undo_manager->undo_stack);
    
    char *text = (char*)undo_manager->undo_stack->data;
    g_signal_handlers_block_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    gtk_text_buffer_set_text(buffer_editor, text, -1);
    g_signal_handlers_unblock_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    update_preview();
}

void redo_perform() {
    if (!undo_manager->redo_stack) return;
    
    char *text = (char*)undo_manager->redo_stack->data;
    undo_manager->undo_stack = g_list_prepend(undo_manager->undo_stack, text);
    undo_manager->redo_stack = g_list_remove_link(undo_manager->redo_stack, undo_manager->redo_stack);
    
    g_signal_handlers_block_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    gtk_text_buffer_set_text(buffer_editor, text, -1);
    g_signal_handlers_unblock_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    update_preview();
}

void undo_free() {
    g_list_free_full(undo_manager->undo_stack, g_free);
    g_list_free_full(undo_manager->redo_stack, g_free);
    g_free(undo_manager);
}

void add_recent_file(const char *filename) {
    for (GList *l = recent_files; l; l = l->next) {
        if (strcmp((char*)l->data, filename) == 0) {
            recent_files = g_list_remove_link(recent_files, l);
            g_free(l->data);
            g_list_free_1(l);
            break;
        }
    }
    recent_files = g_list_prepend(recent_files, g_strdup(filename));
    if (g_list_length(recent_files) > 10) {
        GList *last = g_list_last(recent_files);
        g_free(last->data);
        recent_files = g_list_remove_link(recent_files, last);
        g_list_free_1(last);
    }
    update_recent_menu();
}

void update_toc() {
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer_editor, &start, &end);
    char *text = gtk_text_buffer_get_text(buffer_editor, &start, &end, FALSE);
    
    GList *headers = md_get_headers(text);
    gtk_list_store_clear(toc_store);

    for (GList *l = headers; l != NULL; l = l->next) {
        MdHeader *h = (MdHeader*)l->data;
        GtkTreeIter iter;
        gtk_list_store_append(toc_store, &iter);
        char *indented = g_strdup_printf("%*s%s", (h->level - 1) * 2, "", h->text);
        gtk_list_store_set(toc_store, &iter, COL_TEXT, indented, COL_LINE, h->line, -1);
        g_free(indented);
    }

    md_free_headers(headers);
    g_free(text);
}

static void on_toc_row_activated(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) {
    GtkTreeIter iter;
    if (gtk_tree_model_get_iter(GTK_TREE_MODEL(toc_store), &iter, path)) {
        int line;
        gtk_tree_model_get(GTK_TREE_MODEL(toc_store), &iter, COL_LINE, &line, -1);
        
        GtkTextIter text_iter;
        gtk_text_buffer_get_iter_at_line(buffer_editor, &text_iter, line);
        gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(text_view_editor), &text_iter, 0.0, TRUE, 0.0, 0.0);
        
        gtk_text_buffer_get_iter_at_line(buffer_viewer, &text_iter, line);
        gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(text_view_viewer), &text_iter, 0.0, TRUE, 0.0, 0.0);
    }
}

static gboolean on_viewer_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) {
    GtkTextIter iter;
    int x, y;
    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT, event->x, event->y, &x, &y);
    gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);

    GSList *tags = gtk_text_iter_get_tags(&iter);
    gboolean is_link = FALSE;
    for (GSList *l = tags; l; l = l->next) {
        GtkTextTag *tag = (GtkTextTag*)l->data;
        if (g_object_get_data(G_OBJECT(tag), "url")) {
            is_link = TRUE;
            break;
        }
    }
    g_slist_free(tags);

    GdkCursor *cursor = is_link ? gdk_cursor_new(GDK_HAND2) : NULL;
    gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT), cursor);
    if (cursor) gdk_cursor_unref(cursor);
    
    return FALSE;
}

static gboolean on_viewer_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
    if (event->type == GDK_BUTTON_PRESS) {
        GdkEventButton *bevent = (GdkEventButton *)event;
        GtkTextIter iter;
        int x, y;
        gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT, bevent->x, bevent->y, &x, &y);
        gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, x, y);

        GSList *tags = gtk_text_iter_get_tags(&iter);
        for (GSList *l = tags; l; l = l->next) {
            GtkTextTag *tag = l->data;
            char *url = g_object_get_data(G_OBJECT(tag), "url");
            if (url) {
                char *cmd = g_strdup_printf("xdg-open '%s' &", url);
                system(cmd);
                g_free(cmd);
                g_slist_free(tags);
                return TRUE;
            }
        }
        g_slist_free(tags);
    }
    return FALSE;
}

void update_status_bar() {
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer_editor, &start, &end);
    char *text = gtk_text_buffer_get_text(buffer_editor, &start, &end, FALSE);
    
    int chars = g_utf8_strlen(text, -1);
    int words = 0;
    int in_word = 0;
    
    for (char *p = text; *p; p = g_utf8_next_char(p)) {
        gunichar c = g_utf8_get_char(p);
        if (g_unichar_isspace(c)) {
            in_word = 0;
        } else if (!in_word) {
            in_word = 1;
            words++;
        }
    }
    
    char status_msg[256];
    const char *display_name = current_filename ? g_path_get_basename(current_filename) : "New File";
    snprintf(status_msg, sizeof(status_msg), "%s | Words: %d | Characters: %d", display_name, words, chars);
    if (current_filename) g_free((char*)display_name);

    gtk_statusbar_pop(GTK_STATUSBAR(statusbar), status_context_id);
    gtk_statusbar_push(GTK_STATUSBAR(statusbar), status_context_id, status_msg);
    
    g_free(text);
}

static void on_find_next(GtkWidget *widget, gpointer data) {
    const char *text = gtk_entry_get_text(GTK_ENTRY(find_entry));
    GtkTextIter start, match_start, match_end;
    
    gtk_text_buffer_get_iter_at_mark(buffer_editor, &start, gtk_text_buffer_get_insert(buffer_editor));
    
    if (gtk_text_iter_forward_search(&start, text, GTK_TEXT_SEARCH_VISIBLE_ONLY, &match_start, &match_end, NULL)) {
        gtk_text_buffer_select_range(buffer_editor, &match_start, &match_end);
        gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(text_view_editor), &match_start, 0.0, FALSE, 0.0, 0.0);
    } else {
        // Wrap search
        gtk_text_buffer_get_start_iter(buffer_editor, &start);
        if (gtk_text_iter_forward_search(&start, text, GTK_TEXT_SEARCH_VISIBLE_ONLY, &match_start, &match_end, NULL)) {
            gtk_text_buffer_select_range(buffer_editor, &match_start, &match_end);
            gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(text_view_editor), &match_start, 0.0, FALSE, 0.0, 0.0);
        }
    }
}

static void on_replace(GtkWidget *widget, gpointer data) {
    GtkTextIter start, end;
    if (gtk_text_buffer_get_selection_bounds(buffer_editor, &start, &end)) {
        const char *replacement = gtk_entry_get_text(GTK_ENTRY(replace_entry));
        gtk_text_buffer_delete(buffer_editor, &start, &end);
        gtk_text_buffer_insert(buffer_editor, &start, replacement, -1);
    }
    on_find_next(NULL, NULL);
}

static void on_replace_all(GtkWidget *widget, gpointer data) {
    const char *find_text = gtk_entry_get_text(GTK_ENTRY(find_entry));
    const char *rep_text = gtk_entry_get_text(GTK_ENTRY(replace_entry));
    GtkTextIter iter;
    gtk_text_buffer_get_start_iter(buffer_editor, &iter);
    
    int count = 0;
    GtkTextIter m_start, m_end;
    while (gtk_text_iter_forward_search(&iter, find_text, GTK_TEXT_SEARCH_VISIBLE_ONLY, &m_start, &m_end, NULL)) {
        gtk_text_buffer_delete(buffer_editor, &m_start, &m_end);
        gtk_text_buffer_insert(buffer_editor, &m_start, rep_text, -1);
        iter = m_start;
        gtk_text_iter_forward_chars(&iter, strlen(rep_text));
        count++;
    }
}

static void on_find_activate(GtkMenuItem *item, gpointer user_data) {
    if (find_dialog) {
        gtk_window_present(GTK_WINDOW(find_dialog));
        return;
    }
    
    find_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(find_dialog), "Find and Replace");
    gtk_window_set_transient_for(GTK_WINDOW(find_dialog), GTK_WINDOW(user_data));
    gtk_window_set_destroy_with_parent(GTK_WINDOW(find_dialog), TRUE);
    g_signal_connect(find_dialog, "destroy", G_CALLBACK(gtk_widget_destroyed), &find_dialog);
    
    GtkWidget *vbox = gtk_vbox_new(FALSE, 5);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
    gtk_container_add(GTK_CONTAINER(find_dialog), vbox);
    
    GtkWidget *table = gtk_table_new(2, 2, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(table), 5);
    gtk_table_set_col_spacings(GTK_TABLE(table), 5);
    gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
    
    gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new("Find:"), 0, 1, 0, 1);
    find_entry = gtk_entry_new();
    gtk_table_attach_defaults(GTK_TABLE(table), find_entry, 1, 2, 0, 1);
    
    gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new("Replace with:"), 0, 1, 1, 2);
    replace_entry = gtk_entry_new();
    gtk_table_attach_defaults(GTK_TABLE(table), replace_entry, 1, 2, 1, 2);
    
    GtkWidget *bbox = gtk_hbutton_box_new();
    gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
    
    GtkWidget *btn_find = gtk_button_new_with_label("Find Next");
    g_signal_connect(btn_find, "clicked", G_CALLBACK(on_find_next), NULL);
    gtk_container_add(GTK_CONTAINER(bbox), btn_find);
    
    GtkWidget *btn_replace = gtk_button_new_with_label("Replace");
    g_signal_connect(btn_replace, "clicked", G_CALLBACK(on_replace), NULL);
    gtk_container_add(GTK_CONTAINER(bbox), btn_replace);
    
    GtkWidget *btn_all = gtk_button_new_with_label("Replace All");
    g_signal_connect(btn_all, "clicked", G_CALLBACK(on_replace_all), NULL);
    gtk_container_add(GTK_CONTAINER(bbox), btn_all);
    
    gtk_widget_show_all(find_dialog);
}

void save_file(const char *filename);

void update_line_numbers() {
    int lines = gtk_text_buffer_get_line_count(buffer_editor);
    GString *nums = g_string_new("");
    for (int i = 1; i <= lines; i++) {
        g_string_append_printf(nums, "%d\n", i);
    }
    gtk_text_buffer_set_text(buffer_line_nums, nums->str, -1);
    g_string_free(nums, TRUE);
}

void update_preview() {
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer_editor, &start, &end);
    char *text = gtk_text_buffer_get_text(buffer_editor, &start, &end, FALSE);
    
    // Highlight editor
    md_render_highlight_editor(buffer_editor, dark_mode);
    
    // Render viewer
    md_render_to_buffer(buffer_viewer, text, dark_mode);
    
    // Update Table of Contents
    update_toc();
    
    g_free(text);
}

static void on_wrap_toggled(GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
    line_wrap = gtk_check_menu_item_get_active(checkmenuitem);
    GtkWrapMode mode = line_wrap ? GTK_WRAP_WORD : GTK_WRAP_NONE;
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view_editor), mode);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view_viewer), mode);
}

static void on_dark_mode_toggled(GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
    dark_mode = gtk_check_menu_item_get_active(checkmenuitem);
    update_ui_colors();
    update_preview();
}

static void on_toc_toggled(GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
    show_toc = gtk_check_menu_item_get_active(checkmenuitem);
    if (show_toc) {
        gtk_widget_show(toc_sidebar);
    } else {
        gtk_widget_hide(toc_sidebar);
    }
}

static void on_auto_save_toggled(GtkCheckMenuItem *checkmenuitem, gpointer user_data) {
    auto_save_enabled = gtk_check_menu_item_get_active(checkmenuitem);
}

static void insert_markdown_marker(const char *marker_start, const char *marker_end) {
    GtkTextIter start, end;
    if (gtk_text_buffer_get_selection_bounds(buffer_editor, &start, &end)) {
        gtk_text_buffer_begin_user_action(buffer_editor);
        gtk_text_buffer_insert(buffer_editor, &end, marker_end, -1);
        gtk_text_buffer_insert(buffer_editor, &start, marker_start, -1);
        gtk_text_buffer_end_user_action(buffer_editor);
    } else {
        gtk_text_buffer_insert_at_cursor(buffer_editor, marker_start, -1);
        gtk_text_buffer_insert_at_cursor(buffer_editor, marker_end, -1);
        GtkTextIter cursor;
        gtk_text_buffer_get_iter_at_mark(buffer_editor, &cursor, gtk_text_buffer_get_insert(buffer_editor));
        gtk_text_iter_backward_chars(&cursor, strlen(marker_end));
        gtk_text_buffer_place_cursor(buffer_editor, &cursor);
    }
}

static void on_export_activate(GtkMenuItem *item, gpointer user_data) {
    GtkWidget *dialog = gtk_file_chooser_dialog_new("Export to HTML", GTK_WINDOW(user_data),
                                                   GTK_FILE_CHOOSER_ACTION_SAVE,
                                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                                   GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
        char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
        GtkTextIter start, end;
        gtk_text_buffer_get_bounds(buffer_editor, &start, &end);
        char *text = gtk_text_buffer_get_text(buffer_editor, &start, &end, FALSE);
        char *html = md_to_html(text);
        g_file_set_contents(filename, html, -1, NULL);
        g_free(text);
        g_free(html);
        g_free(filename);
    }
    gtk_widget_destroy(dialog);
}

static void on_print_draw_page(GtkPrintOperation *operation, GtkPrintContext *context, int page_nr, gpointer user_data) {
    cairo_t *cr = gtk_print_context_get_cairo_context(context);
    PangoLayout *layout = gtk_print_context_create_pango_layout(context);
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer_viewer, &start, &end);
    char *text = gtk_text_buffer_get_text(buffer_viewer, &start, &end, FALSE);
    pango_layout_set_text(layout, text, -1);
    pango_layout_set_width(layout, gtk_print_context_get_width(context) * PANGO_SCALE);
    pango_cairo_show_layout(cr, layout);
    g_free(text);
    g_object_unref(layout);
}

static void on_print_activate(GtkMenuItem *item, gpointer user_data) {
    GtkPrintOperation *print = gtk_print_operation_new();
    gtk_print_operation_set_n_pages(print, 1);
    g_signal_connect(print, "draw-page", G_CALLBACK(on_print_draw_page), NULL);
    gtk_print_operation_run(print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(user_data), NULL);
    g_object_unref(print);
}

static gboolean debounce_cb(gpointer user_data) {
    update_preview();
    GtkTextIter start;
    gtk_text_buffer_get_start_iter(buffer_viewer, &start);
    gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(text_view_viewer), &start, 0.0, TRUE, 0.0, 0.0);
    debounce_id = 0;
    return FALSE;
}

static void on_new_activate(GtkMenuItem *item, gpointer user_data) {
    g_signal_handlers_block_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    gtk_text_buffer_set_text(buffer_editor, "", 0);
    g_signal_handlers_unblock_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    
    if (current_filename) {
        g_free(current_filename);
        current_filename = NULL;
    }
    
    g_list_free_full(undo_manager->undo_stack, g_free);
    g_list_free_full(undo_manager->redo_stack, g_free);
    undo_manager->undo_stack = NULL;
    undo_manager->redo_stack = NULL;
    undo_push("");
    
    update_preview();
}

static void on_editor_changed(GtkTextBuffer *buffer, gpointer user_data) {
    if (debounce_id) g_source_remove(debounce_id);
    debounce_id = g_timeout_add(300, debounce_cb, NULL);
    
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer, &start, &end);
    char *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
    undo_push(text);
    g_free(text);

    update_status_bar();
    if (auto_save_enabled && current_filename) {
        save_file(current_filename);
    }
}

void save_file(const char *filename) {
    if (!filename) return;

    FILE *f = fopen(filename, "w");
    if (!f) {
        perror("fopen");
        return;
    }

    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer_editor, &start, &end);
    char *text = gtk_text_buffer_get_text(buffer_editor, &start, &end, FALSE);
    fputs(text, f);
    g_free(text);
    fclose(f);
}

static void on_save_activate(GtkMenuItem *item, gpointer user_data) {
    if (current_filename) {
        save_file(current_filename);
    } else {
        GtkWidget *dialog;
        dialog = gtk_file_chooser_dialog_new("Save File",
                                            GTK_WINDOW(user_data),
                                            GTK_FILE_CHOOSER_ACTION_SAVE,
                                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                            GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                                            NULL);
        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);

        if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
            char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
            save_file(filename);
            if (current_filename) g_free(current_filename);
            current_filename = g_strdup(filename);
            g_free(filename);
            add_recent_file(current_filename);
        }
        gtk_widget_destroy(dialog);
    }
}

static void on_open_activate(GtkMenuItem *item, gpointer user_data) {
    GtkWidget *dialog;
    dialog = gtk_file_chooser_dialog_new("Open File",
                                        GTK_WINDOW(user_data),
                                        GTK_FILE_CHOOSER_ACTION_OPEN,
                                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                        GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
                                        NULL);

    if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
        char *filename;
        filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
        load_file(filename);
        g_free(filename);
    }
    gtk_widget_destroy(dialog);
}

void load_file(const char *filename) {
    FILE *f = fopen(filename, "r");
    if (!f) {
        perror("fopen");
        return;
    }

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);

    char *content = malloc(size + 1);
    fread(content, 1, size, f);
    content[size] = '\0';
    fclose(f);

    if (current_filename) g_free(current_filename);
    current_filename = g_strdup(filename);
    add_recent_file(filename);

    g_signal_handlers_block_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    gtk_text_buffer_set_text(buffer_editor, content, -1);
    g_signal_handlers_unblock_by_func(buffer_editor, G_CALLBACK(on_editor_changed), NULL);
    
    update_preview();
    update_line_numbers();
    update_status_bar();
    free(content);
}

static void on_bold_shortcut() { insert_markdown_marker("**", "**"); }
static void on_italic_shortcut() { insert_markdown_marker("*", "*"); }
static void on_link_shortcut() { insert_markdown_marker("[", "](url)"); }
static void on_list_shortcut() { insert_markdown_marker("- ", ""); }
static void on_image_shortcut() { insert_markdown_marker("![Alt text](", ")"); }

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);
    load_config();

    undo_manager = g_malloc0(sizeof(UndoManager));
    undo_manager->max_depth = 50;

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "GTK2 Markdown Editor & Viewer");
    gtk_window_set_default_size(GTK_WINDOW(window), 1000, 700);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    GtkAccelGroup *accel_group = gtk_accel_group_new();
    gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);

    GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    // Menu
    GtkWidget *menubar = gtk_menu_bar_new();
    
    // File Menu
    GtkWidget *file_menu_item = gtk_menu_item_new_with_label("File");
    GtkWidget *file_menu = gtk_menu_new();
    GtkWidget *new_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW, NULL);
    GtkWidget *open_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
    GtkWidget *recent_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
    gtk_menu_item_set_label(GTK_MENU_ITEM(recent_item), "Open Recent");
    recent_menu = gtk_menu_new();
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
    update_recent_menu();

    GtkWidget *save_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_SAVE, NULL);
    GtkWidget *export_item = gtk_menu_item_new_with_label("Export to HTML");
    GtkWidget *print_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PRINT, NULL);
    GtkWidget *auto_save_item = gtk_check_menu_item_new_with_label("Auto-Save");
    GtkWidget *sep = gtk_separator_menu_item_new();
    GtkWidget *quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);

    gtk_menu_item_set_submenu(GTK_MENU_ITEM(file_menu_item), file_menu);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), new_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), open_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), recent_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), save_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), export_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), print_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), auto_save_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), sep);
    gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), quit_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file_menu_item);

    // Edit Menu
    GtkWidget *edit_menu_item = gtk_menu_item_new_with_label("Edit");
    GtkWidget *edit_menu = gtk_menu_new();
    GtkWidget *undo_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_UNDO, NULL);
    GtkWidget *redo_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_REDO, NULL);
    
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(edit_menu_item), edit_menu);
    gtk_menu_shell_append(GTK_MENU_SHELL(edit_menu), undo_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(edit_menu), redo_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(edit_menu), gtk_separator_menu_item_new());
    
    GtkWidget *find_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_FIND, NULL);
    g_signal_connect(find_item, "activate", G_CALLBACK(on_find_activate), window);
    gtk_menu_shell_append(GTK_MENU_SHELL(edit_menu), find_item);

    GtkWidget *rep_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_FIND_AND_REPLACE, NULL);
    g_signal_connect(rep_item, "activate", G_CALLBACK(on_find_activate), window);
    gtk_menu_shell_append(GTK_MENU_SHELL(edit_menu), rep_item);

    gtk_menu_shell_append(GTK_MENU_SHELL(menubar), edit_menu_item);

    // View Menu
    GtkWidget *view_menu_item = gtk_menu_item_new_with_label("View");
    GtkWidget *view_menu = gtk_menu_new();
    GtkWidget *wrap_item = gtk_check_menu_item_new_with_label("Line Wrap");
    GtkWidget *dark_item = gtk_check_menu_item_new_with_label("Dark Mode");
    GtkWidget *toc_toggle_item = gtk_check_menu_item_new_with_label("Show Table of Contents");
    
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(wrap_item), line_wrap);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(dark_item), dark_mode);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(toc_toggle_item), show_toc);
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(auto_save_item), auto_save_enabled);

    gtk_menu_item_set_submenu(GTK_MENU_ITEM(view_menu_item), view_menu);
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), wrap_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), dark_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), toc_toggle_item);
    gtk_menu_shell_append(GTK_MENU_SHELL(menubar), view_menu_item);

    gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

    g_signal_connect(new_item, "activate", G_CALLBACK(on_new_activate), window);
    g_signal_connect(open_item, "activate", G_CALLBACK(on_open_activate), window);
    g_signal_connect(save_item, "activate", G_CALLBACK(on_save_activate), window);
    g_signal_connect(export_item, "activate", G_CALLBACK(on_export_activate), window);
    g_signal_connect(print_item, "activate", G_CALLBACK(on_print_activate), window);
    g_signal_connect(auto_save_item, "activate", G_CALLBACK(on_auto_save_toggled), NULL);
    g_signal_connect(quit_item, "activate", G_CALLBACK(gtk_main_quit), NULL);
    
    g_signal_connect(undo_item, "activate", G_CALLBACK(undo_perform), NULL);
    g_signal_connect(redo_item, "activate", G_CALLBACK(redo_perform), NULL);
    g_signal_connect(wrap_item, "toggled", G_CALLBACK(on_wrap_toggled), NULL);
    g_signal_connect(dark_item, "toggled", G_CALLBACK(on_dark_mode_toggled), NULL);
    g_signal_connect(toc_toggle_item, "toggled", G_CALLBACK(on_toc_toggled), NULL);

    // Shortcuts
    GClosure *new_closure = g_cclosure_new(G_CALLBACK(on_new_activate), window, NULL);
    gtk_accel_group_connect(accel_group, GDK_n, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, new_closure);
    GClosure *bold_closure = g_cclosure_new(G_CALLBACK(on_bold_shortcut), NULL, NULL);
    gtk_accel_group_connect(accel_group, GDK_b, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, bold_closure);
    GClosure *italic_closure = g_cclosure_new(G_CALLBACK(on_italic_shortcut), NULL, NULL);
    gtk_accel_group_connect(accel_group, GDK_i, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, italic_closure);
    GClosure *link_closure = g_cclosure_new(G_CALLBACK(on_link_shortcut), NULL, NULL);
    gtk_accel_group_connect(accel_group, GDK_k, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, link_closure);

    GClosure *undo_closure = g_cclosure_new(G_CALLBACK(undo_perform), NULL, NULL);
    gtk_accel_group_connect(accel_group, GDK_z, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, undo_closure);
    GClosure *redo_closure = g_cclosure_new(G_CALLBACK(redo_perform), NULL, NULL);
    gtk_accel_group_connect(accel_group, GDK_y, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, redo_closure);
    
    GClosure *find_closure = g_cclosure_new(G_CALLBACK(on_find_activate), window, NULL);
    gtk_accel_group_connect(accel_group, GDK_f, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, find_closure);
    GClosure *rep_closure = g_cclosure_new(G_CALLBACK(on_find_activate), window, NULL);
    gtk_accel_group_connect(accel_group, GDK_h, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, rep_closure);

    // Outer Paned (ToC | Contents)
    GtkWidget *outer_paned = gtk_hpaned_new();
    gtk_box_pack_start(GTK_BOX(vbox), outer_paned, TRUE, TRUE, 0);

    // ToC Sidebar (Left)
    toc_sidebar = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(toc_sidebar), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_paned_add1(GTK_PANED(outer_paned), toc_sidebar);
    
    toc_store = gtk_list_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_INT);
    GtkWidget *toc_list = gtk_tree_view_new_with_model(GTK_TREE_MODEL(toc_store));
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(toc_list), FALSE);
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
    GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("Header", renderer, "text", COL_TEXT, NULL);
    gtk_tree_view_append_column(GTK_TREE_VIEW(toc_list), column);
    gtk_container_add(GTK_CONTAINER(toc_sidebar), toc_list);
    g_signal_connect(toc_list, "row-activated", G_CALLBACK(on_toc_row_activated), NULL);

    // Inner Paned (Editor | Viewer)
    GtkWidget *inner_paned = gtk_hpaned_new();
    gtk_paned_add2(GTK_PANED(outer_paned), inner_paned);

    // Editor (Mid)
    GtkWidget *sw_editor = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_editor), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_paned_add1(GTK_PANED(inner_paned), sw_editor);

    GtkWidget *editor_hbox = gtk_hbox_new(FALSE, 0);
    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw_editor), editor_hbox);

    line_num_view = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(line_num_view), FALSE);
    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(line_num_view), FALSE);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(line_num_view), 5);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(line_num_view), 5);
    gtk_widget_set_sensitive(line_num_view, FALSE);
    buffer_line_nums = gtk_text_view_get_buffer(GTK_TEXT_VIEW(line_num_view));
    gtk_box_pack_start(GTK_BOX(editor_hbox), line_num_view, FALSE, FALSE, 0);

    text_view_editor = gtk_text_view_new();
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view_editor), line_wrap ? GTK_WRAP_WORD : GTK_WRAP_NONE);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_editor), 10);
    gtk_box_pack_start(GTK_BOX(editor_hbox), text_view_editor, TRUE, TRUE, 0);
    buffer_editor = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view_editor));
    g_signal_connect(buffer_editor, "changed", G_CALLBACK(on_editor_changed), NULL);

    // Viewer (Right)
    GtkWidget *sw_viewer = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_viewer), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_paned_add2(GTK_PANED(inner_paned), sw_viewer);

    text_view_viewer = gtk_text_view_new();
    gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view_viewer), FALSE);
    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text_view_viewer), FALSE);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view_viewer), line_wrap ? GTK_WRAP_WORD : GTK_WRAP_NONE);
    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_viewer), 20);
    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_view_viewer), 20);
    gtk_container_add(GTK_CONTAINER(sw_viewer), text_view_viewer);
    buffer_viewer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view_viewer));
    gtk_widget_add_events(text_view_viewer, GDK_POINTER_MOTION_MASK);
    g_signal_connect(text_view_viewer, "button-press-event", G_CALLBACK(on_viewer_event), NULL);
    g_signal_connect(text_view_viewer, "motion-notify-event", G_CALLBACK(on_viewer_motion_notify), NULL);

    // Status Bar
    statusbar = gtk_statusbar_new();
    gtk_box_pack_start(GTK_BOX(vbox), statusbar, FALSE, FALSE, 0);
    status_context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "main");

    // Initialize Markdown tags
    md_render_init_tags(buffer_editor);
    md_render_init_tags(buffer_viewer);

    update_ui_colors();
    gtk_paned_set_position(GTK_PANED(outer_paned), 200);
    gtk_paned_set_position(GTK_PANED(inner_paned), 400);

    if (!show_toc) gtk_widget_hide(toc_sidebar);

    if (argc > 1) {
        load_file(argv[1]);
    } else {
        gtk_text_buffer_set_text(buffer_editor, "# Welcome\n\nType here to see live preview on the right.", -1);
    }

    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer_editor, &start, &end);
    char *initial_text = gtk_text_buffer_get_text(buffer_editor, &start, &end, FALSE);
    undo_push(initial_text);
    g_free(initial_text);

    gtk_widget_show_all(window);
    if (!show_toc) gtk_widget_hide(toc_sidebar);
    update_status_bar();
    gtk_main();

    save_config();
    if (undo_manager) undo_free();
    if (current_filename) g_free(current_filename);
    
    return 0;
}
