/* myapp-window.c
 *
 * Copyright 2025 Giovanni
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */


#include "adwaita.h"
#include "gtk/gtk.h"
#include "meme-window-private.h"
#include "meme-fileio.h"
#include "meme-canvas.h"
#include "meme-application.h"
#include <glib/gstdio.h>
#include <stdio.h>
#include "config.h"

G_DEFINE_FINAL_TYPE (MemeWindow, meme_window, ADW_TYPE_APPLICATION_WINDOW)

static void populate_template_gallery (MemeWindow *self);
static void set_template_select_mode (MemeWindow *self, gboolean active);
static void update_template_gallery_empty_state (MemeWindow *self);
static guint count_flowbox_children (GtkFlowBox *flowbox);

static void draw_crop_overlay (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data) {
    MemeWindow *self = MEME_WINDOW (user_data);
    double img_w, img_h, scale, off_x, off_y;

    if (!self->template_image || !gtk_toggle_button_get_active (self->crop_mode_button))
        return;

    img_w = gdk_pixbuf_get_width (self->template_image);
    img_h = gdk_pixbuf_get_height (self->template_image);
    if (width <= 0 || height <= 0 || img_w <= 0 || img_h <= 0) return;

    scale = MIN (width / img_w, height / img_h);
    off_x = (width - img_w * scale) / 2.0;
    off_y = (height - img_h * scale) / 2.0;

    meme_draw_crop_chrome (cr, width, height,
        off_x + self->crop_x * img_w * scale,
        off_y + self->crop_y * img_h * scale,
        self->crop_w * img_w * scale,
        self->crop_h * img_h * scale);
}

void render_meme (MemeWindow *self) {
    gboolean is_dragging, is_crop_drag, crop_active, cinematic, deepfry, bw_button;
    GdkTexture *tex;

    if (!self->template_image) return;
    
    is_dragging = (self->drag_type != DRAG_TYPE_NONE);
    is_crop_drag = (self->drag_type == DRAG_TYPE_CROP_MOVE ||
                             self->drag_type == DRAG_TYPE_CROP_RESIZE);
    crop_active = gtk_toggle_button_get_active(self->crop_mode_button);
    cinematic = gtk_toggle_button_get_active(self->cinematic_button);
    deepfry = gtk_toggle_button_get_active(self->deep_fry_button);
    bw_button = gtk_toggle_button_get_active(self->bw_button);

    if (!self->final_meme || !is_crop_drag) {
        if (self->final_meme) g_object_unref(self->final_meme);
        self->final_meme = meme_render_composite(self->template_image,
                                        self->layers,
                                        cinematic,
                                        deepfry,
                                        bw_button,
                                        is_dragging);
    }
    gtk_widget_queue_draw(GTK_WIDGET(self->meme_preview));

    if (crop_active || (is_dragging && !is_crop_drag)) {
        tex = gdk_texture_new_for_pixbuf(self->final_meme);
    } else {
        tex = meme_render_editor_overlay(
            self->final_meme, self->layers, self->selected_layer,
            FALSE, 0, 0, 0, 0
        );
    }

    gtk_picture_set_paintable(self->meme_preview, GDK_PAINTABLE(tex));
    g_object_unref(tex);

    gtk_widget_queue_draw(GTK_WIDGET(self->crop_overlay_area));
}

static void on_color_changed (GObject *object, GParamSpec *pspec, MemeWindow *self) {
    if (self->selected_layer && self->selected_layer->type == LAYER_TYPE_TEXT) {
        const GdkRGBA *tc = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(self->text_color_btn));
        const GdkRGBA *sc = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(self->stroke_color_btn));
        
        if (tc) self->selected_layer->text_color = *tc;
        if (sc) self->selected_layer->stroke_color = *sc;
        
        render_meme(self);
        g_clear_object(&self->selected_layer->pixbuf);
        self->selected_layer->pixbuf = NULL;
    }
}

static void on_text_changed (MemeWindow *self) { if (self->template_image) render_meme (self); }
static void on_deep_fry_toggled (GtkToggleButton *btn, MemeWindow *self) { render_meme (self); }

static void on_layer_text_changed (MemeWindow *self) {
    if (self->selected_layer && self->selected_layer->type == LAYER_TYPE_TEXT) {
        GtkTextBuffer *buffer;
        GtkTextIter start, end;

        g_free (self->selected_layer->text);
        buffer = gtk_text_view_get_buffer (self->layer_text_view);
        gtk_text_buffer_get_bounds (buffer, &start, &end);
        
        self->selected_layer->text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
        self->selected_layer->font_size = gtk_spin_button_get_value (self->layer_font_size);
        render_meme (self);
        g_clear_object(&self->selected_layer->pixbuf);
        self->selected_layer->pixbuf = NULL;
    }
}

static void on_add_text_clicked (MemeWindow *self) {
    ImageLayer *new_layer;

    push_undo (self);
    new_layer = g_new0 (ImageLayer, 1);
    new_layer->type = LAYER_TYPE_TEXT;
    new_layer->text = g_strdup ("Text");
    new_layer->font_size = 60.0;
    new_layer->x = 0.5; new_layer->y = 0.5;
    new_layer->scale = 1.0; new_layer->opacity = 1.0;
    new_layer->blend_mode = BLEND_NORMAL;
    self->layers = g_list_append (self->layers, new_layer);
    self->selected_layer = new_layer;
    sync_ui_with_layer(self);
    render_meme (self);
}   

void update_template_image (MemeWindow *self, GdkPixbuf *new_pixbuf) {
    if (!new_pixbuf) return;
    push_undo (self);
    if (self->template_image) g_object_unref (self->template_image);
    self->template_image = new_pixbuf;
    render_meme (self);
}

static void on_rotate_clicked (GtkWidget *btn, MemeWindow *self) {
    gboolean clockwise;
    GdkPixbuf *new_pix;
    if (!self->template_image) return;
    clockwise = (btn == GTK_WIDGET (self->rotate_right_button)) ||
                (btn == GTK_WIDGET (self->footer_rotate_right_button));
    new_pix = gdk_pixbuf_rotate_simple (self->template_image,
    clockwise ? GDK_PIXBUF_ROTATE_CLOCKWISE : GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
    update_template_image (self, new_pix);
    meme_window_transform_gif_frames_rotate (self, clockwise);
    if (gtk_toggle_button_get_active (self->crop_mode_button)) {
        self->crop_x = 0.0; self->crop_y = 0.0;
        self->crop_w = 1.0; self->crop_h = 1.0;
        render_meme (self);
    }
}

static void on_flip_clicked (GtkWidget *btn, MemeWindow *self) {
    gboolean horizontal;
    GdkPixbuf *new_pix;
    if (!self->template_image) return;
    horizontal = (btn == GTK_WIDGET (self->flip_h_button)) ||
                 (btn == GTK_WIDGET (self->footer_flip_h_button));
    new_pix = gdk_pixbuf_flip (self->template_image, horizontal);
    update_template_image (self, new_pix);
    meme_window_transform_gif_frames_flip (self, horizontal);
    // Same reasoning as rotate: don't let a stale crop selection carry
    // over onto the flipped image.
    if (gtk_toggle_button_get_active (self->crop_mode_button)) {
        self->crop_x = 0.0; self->crop_y = 0.0;
        self->crop_w = 1.0; self->crop_h = 1.0;
        render_meme (self);
    }
}

static void on_crop_preset_clicked (GtkWidget *btn, MemeWindow *self) {
    int w, h;
    double target_ratio = 1.0;
    double current_ratio;
    if (!self->template_image) return;
    w = gdk_pixbuf_get_width (self->template_image);
    h = gdk_pixbuf_get_height (self->template_image);
    current_ratio = (double)w / (double)h;

    if (btn == GTK_WIDGET (self->crop_square_button) || btn == GTK_WIDGET (self->footer_crop_square_button))
        target_ratio = 1.0;
    else if (btn == GTK_WIDGET (self->crop_43_button) || btn == GTK_WIDGET (self->footer_crop_43_button))
        target_ratio = 4.0/3.0;
    else if (btn == GTK_WIDGET (self->crop_169_button) || btn == GTK_WIDGET (self->footer_crop_169_button))
        target_ratio = 16.0/9.0;

    if (current_ratio > target_ratio) {
        self->crop_h = 1.0;
        self->crop_w = target_ratio / current_ratio;
        self->crop_x = (1.0 - self->crop_w) / 2.0;
        self->crop_y = 0.0;
    } else {
        self->crop_w = 1.0;
        self->crop_h = current_ratio / target_ratio;
        self->crop_y = (1.0 - self->crop_h) / 2.0;
        self->crop_x = 0.0;
    }
    render_meme(self);
}

static void update_footer_pages (MemeWindow *self) {
    gboolean crop_active = gtk_toggle_button_get_active (self->crop_mode_button);
    gboolean is_text = (self->selected_layer != NULL && self->selected_layer->type == LAYER_TYPE_TEXT);

    gtk_widget_set_visible (GTK_WIDGET (self->footer_tools_page), !crop_active && !is_text);
    gtk_widget_set_visible (GTK_WIDGET (self->footer_text_page), !crop_active && is_text);
}

static void on_exit_text_editing_clicked (MemeWindow *self) {
    self->selected_layer = NULL;
    sync_ui_with_layer (self);
    render_meme (self);
}

static void on_open_template_window_clicked (MemeWindow *self) {
    //its just like seeing her, for the first time again.
    if (self->template_select_mode)
        set_template_select_mode (self, FALSE);
    adw_dialog_present (self->template_window, GTK_WIDGET (self));
}

static void on_crop_mode_toggled (GtkToggleButton *btn, MemeWindow *self) {
    gboolean active = gtk_toggle_button_get_active (btn);
    gtk_widget_set_visible (GTK_WIDGET (self->transform_group), active);
    gtk_widget_set_visible (GTK_WIDGET (self->layer_group), !active);
    gtk_widget_set_visible (GTK_WIDGET (self->layer_group), !active && self->selected_layer != NULL);
    if (active) {
    meme_window_pause_gif_animation (self);
    self->crop_x = 0.0; self->crop_y = 0.0;
    self->crop_w = 1.0; self->crop_h = 1.0;
    g_clear_object (&self->crop_session_template_snapshot);
    if (self->template_image)
        self->crop_session_template_snapshot = g_object_ref (self->template_image);
    } else {
        gtk_widget_set_cursor (GTK_WIDGET (self->meme_preview), NULL);
        meme_window_resume_gif_animation (self);
    }
    update_footer_pages (self);
    render_meme(self);
}

static void on_cancel_crop_clicked (MemeWindow *self) {
    // Restore the image exactly as it was when crop mode was entered,
    // undoing any rotate/flip done mid-session. The undo_stack only
    // tracks layers, not template_image, so we can't use it here.
    if (self->crop_session_template_snapshot) {
        if (self->template_image) g_object_unref (self->template_image);
        self->template_image = self->crop_session_template_snapshot;
        self->crop_session_template_snapshot = NULL;
    }
    self->crop_x = 0.0; self->crop_y = 0.0;
    self->crop_w = 1.0; self->crop_h = 1.0;
    gtk_toggle_button_set_active (self->crop_mode_button, FALSE);
    render_meme (self);
}

static void on_apply_crop_clicked (MemeWindow *self) {
    GdkPixbuf *sub;
    GdkPixbuf *new_pix;
    int iw, ih, x, y, w, h;
    GList *l;

    if (!self->template_image) return;
    iw = gdk_pixbuf_get_width(self->template_image);
    ih = gdk_pixbuf_get_height(self->template_image);
    x = self->crop_x * iw;
    y = self->crop_y * ih;
    w = self->crop_w * iw;
    h = self->crop_h * ih;
    if (w <= 0 || h <= 0) return;

    push_undo(self);

    for (l = self->layers; l != NULL; l = l->next) {
        ImageLayer *layer = (ImageLayer *)l->data;
        double abs_x = layer->x * iw;
        double abs_y = layer->y * ih;
        layer->x = (abs_x - x) / (double)w;
        layer->y = (abs_y - y) / (double)h;
    }
    sub = gdk_pixbuf_new_subpixbuf(self->template_image, x, y, w, h);
    new_pix = gdk_pixbuf_copy(sub);
    g_object_unref(sub);
    update_template_image(self, new_pix); 
    meme_window_transform_gif_frames_crop (self, x, y, w, h);
    self->crop_x = 0; self->crop_y = 0; self->crop_w = 1; self->crop_h = 1;
    gtk_toggle_button_set_active(self->crop_mode_button, FALSE);
    g_clear_object (&self->crop_session_template_snapshot);
}

static void on_font_changed (GObject *object, GParamSpec *pspec, MemeWindow *self) {
    if (self->selected_layer && self->selected_layer->type == LAYER_TYPE_TEXT) {
        PangoFontDescription *desc = gtk_font_dialog_button_get_font_desc (self->font_choose_btn);
        if (desc) {
            g_free (self->selected_layer->font_family);
            self->selected_layer->font_family = pango_font_description_to_string (desc);
            render_meme (self);
            self->selected_layer->pixbuf = NULL;
        }
    }
}

void sync_ui_with_layer(MemeWindow *self) {
    GtkTextBuffer *buffer;

    gboolean sensitive = (self->selected_layer != NULL);
    gboolean is_text = (sensitive && self->selected_layer->type == LAYER_TYPE_TEXT);
    gboolean is_crop = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(self->crop_mode_button));    
    g_signal_handlers_block_by_func(self->layer_opacity_scale, on_text_changed, self);

    g_signal_handlers_block_by_func(self->layer_opacity_scale, on_text_changed, self);
    g_signal_handlers_block_by_func(self->layer_rotation_scale, on_text_changed, self);
    buffer = gtk_text_view_get_buffer (self->layer_text_view);
    g_signal_handlers_block_by_func(buffer, on_layer_text_changed, self);
    g_signal_handlers_block_by_func(self->layer_font_size, on_layer_text_changed, self);

    if (sensitive) {
        gtk_range_set_value(GTK_RANGE(self->layer_opacity_scale), self->selected_layer->opacity);
        gtk_range_set_value(GTK_RANGE(self->layer_rotation_scale), self->selected_layer->rotation);
        adw_combo_row_set_selected(self->blend_mode_row, self->selected_layer->blend_mode);
        if (is_text) {
            gtk_text_buffer_set_text(buffer, self->selected_layer->text ? self->selected_layer->text : "", -1);
            gtk_spin_button_set_value(self->layer_font_size, self->selected_layer->font_size);
        }
    }
    gtk_widget_set_visible(GTK_WIDGET(self->layer_group), sensitive && !is_crop);
    gtk_widget_set_visible(GTK_WIDGET(self->layer_text_container), is_text);
    gtk_widget_set_visible(GTK_WIDGET(self->layer_font_size_row), is_text);
    gtk_widget_set_sensitive(GTK_WIDGET(self->layer_opacity_scale), sensitive);
    gtk_widget_set_sensitive(GTK_WIDGET(self->layer_rotation_scale), sensitive);
    gtk_widget_set_sensitive(GTK_WIDGET(self->blend_mode_row), sensitive);
    gtk_widget_set_sensitive(GTK_WIDGET(self->delete_layer_button), sensitive);
    
    g_signal_handlers_unblock_by_func(self->layer_opacity_scale, on_text_changed, self);
    g_signal_handlers_unblock_by_func(self->layer_rotation_scale, on_text_changed, self);
    g_signal_handlers_unblock_by_func(buffer, on_layer_text_changed, self);
    g_signal_handlers_unblock_by_func(self->layer_font_size, on_layer_text_changed, self);

    gtk_widget_set_visible (GTK_WIDGET (self->font_choose_row), is_text);
    g_signal_handlers_block_by_func(self->text_color_btn, on_color_changed, self);
    g_signal_handlers_block_by_func(self->stroke_color_btn, on_color_changed, self);

    if (sensitive && is_text) {
        gtk_color_dialog_button_set_rgba(GTK_COLOR_DIALOG_BUTTON(self->text_color_btn), &self->selected_layer->text_color);
        gtk_color_dialog_button_set_rgba(GTK_COLOR_DIALOG_BUTTON(self->stroke_color_btn), &self->selected_layer->stroke_color);
    }

    g_signal_handlers_unblock_by_func(self->text_color_btn, on_color_changed, self);
    g_signal_handlers_unblock_by_func(self->stroke_color_btn, on_color_changed, self);
    
    if (is_text && self->selected_layer->font_family) {
        PangoFontDescription *desc;

        g_signal_handlers_block_by_func (self->font_choose_btn, on_font_changed, self);
        desc = pango_font_description_from_string (self->selected_layer->font_family);
        gtk_font_dialog_button_set_font_desc (self->font_choose_btn, desc);
        pango_font_description_free (desc);
        g_signal_handlers_unblock_by_func (self->font_choose_btn, on_font_changed, self);
    }

    update_footer_pages (self);
}

static void on_layer_control_changed (MemeWindow *self) {
    if (self->selected_layer) {
        self->selected_layer->opacity = gtk_range_get_value(GTK_RANGE(self->layer_opacity_scale));
        self->selected_layer->rotation = gtk_range_get_value(GTK_RANGE(self->layer_rotation_scale));
        self->selected_layer->blend_mode = (BlendMode)adw_combo_row_get_selected(self->blend_mode_row);
        render_meme(self);
    }
}

static void on_delete_layer_clicked (MemeWindow *self) {
    if (self->selected_layer) {
        push_undo (self);
        self->layers = g_list_remove(self->layers, self->selected_layer);
        meme_layer_free(self->selected_layer);
        self->selected_layer = NULL;
        sync_ui_with_layer(self);
        render_meme(self);
    }
}

void on_clear_clicked (MemeWindow *self) {
    meme_window_stop_gif_animation (self);
    gtk_stack_set_visible_child_name (self->content_stack, "empty");
    g_clear_object (&self->template_image);
    g_clear_object (&self->final_meme);
    g_clear_object (&self->crop_session_template_snapshot);
    if (self->layers) { meme_layer_list_free (self->layers); self->layers = NULL; }
    free_history_stack (&self->undo_stack); free_history_stack (&self->redo_stack);
    self->selected_layer = NULL;
    sync_ui_with_layer(self);
    gtk_picture_set_paintable (self->meme_preview, NULL);
    gtk_toggle_button_set_active (self->deep_fry_button, FALSE);
    gtk_toggle_button_set_active (self->cinematic_button, FALSE);
    gtk_toggle_button_set_active (self->crop_mode_button, FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->export_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->global_filters_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->add_text_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->add_image_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->crop_mode_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->save_project_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->zoom_in), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->zoom_out), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->copy_clipboard_button), FALSE);
    gtk_toggle_button_set_active (self->bw_button, FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->bw_button), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(self->clear_button), FALSE);
    self->zoom_level = 1.0;
    gtk_widget_set_size_request(GTK_WIDGET(self->meme_preview), -1, -1); 
}

void on_copy_clipboard_clicked (MemeWindow *self) {
    AdwToast *pill_toast;
    GdkClipboard *clipboard;
    GdkPixbuf *save;
    GdkTexture *texture;

    if (!self->final_meme) return;

    clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self));
    save = self->final_meme;

    if (gtk_toggle_button_get_active(self->crop_mode_button)) {
        int iw = gdk_pixbuf_get_width(save); 
        int ih = gdk_pixbuf_get_height(save);
        save = gdk_pixbuf_new_subpixbuf(save, self->crop_x * iw, self->crop_y * ih, self->crop_w * iw, self->crop_h * ih);
    } else {
        g_object_ref(save);
    }

    texture = gdk_texture_new_for_pixbuf (save);
    gdk_clipboard_set_texture (clipboard, texture);
    g_object_unref (texture);
    g_object_unref (save);
    pill_toast = adw_toast_new("Copied to Clipboard");
    adw_toast_overlay_add_toast(self->copy_clip_feedback, pill_toast);
}

static void myapp_window_finalize (GObject *object) {
    MemeWindow *self = MEME_WINDOW (object);
    meme_window_stop_gif_animation (self);
    g_clear_object (&self->template_image);
    g_clear_object (&self->final_meme);
    g_clear_object (&self->crop_session_template_snapshot);
    g_clear_object (&self->template_window);
    g_clear_object (&self->template_settings);
    g_free (self->template_gif_path);
    if (self->layers) meme_layer_list_free (self->layers);
    free_history_stack (&self->undo_stack);
    free_history_stack (&self->redo_stack);
    G_OBJECT_CLASS (meme_window_parent_class)->finalize (object);
}

static char * get_user_template_dir (void) {
    return g_build_filename (g_get_user_data_dir (), "io.github.vani_tty1.memerist", "templates", NULL);
}

static gboolean is_user_template (const char *path) {
    g_autofree char *user_dir = get_user_template_dir ();
    return g_str_has_prefix (path, user_dir);
}

static void add_file_to_gallery (MemeWindow *self, const char *full_path) {
    GtkWidget *picture;
    gint64 *mtime = g_new0(gint64, 1);

    if (g_str_has_prefix (full_path, "resource://")) {
        picture = gtk_picture_new_for_resource (full_path + 11);
        *mtime = 0; // Built-in resources are technically the "oldest"
    } else {
        GStatBuf stat_buf;
        picture = gtk_picture_new_for_filename (full_path);

        if (g_stat(full_path, &stat_buf) == 0) {
            *mtime = stat_buf.st_mtime;
        }
    }

    gtk_picture_set_can_shrink (GTK_PICTURE (picture), TRUE);
    gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
    gtk_widget_set_size_request (picture, 120, 120);

    g_object_set_data_full (G_OBJECT (picture), "template-path", g_strdup (full_path), g_free);

    g_object_set_data_full (G_OBJECT (picture), "mtime", mtime, g_free);

    gtk_flow_box_append (self->template_gallery, picture);
}

// files are processed in small background batches
// to (hopefully) increases performance
static void on_templates_enumerated (GObject *source_object, GAsyncResult *res, gpointer user_data) {
    GFileEnumerator *enumerator = G_FILE_ENUMERATOR (source_object);
    MemeWindow *self = MEME_WINDOW (user_data);
    GError *error = NULL;
    
    GList *files = g_file_enumerator_next_files_finish (enumerator, res, &error);

    if (error) {
        g_printerr ("Error reading templates: %s\n", error->message);
        g_clear_error (&error);
        g_object_unref (enumerator);
        return;
    }

    // if no more files, we are done
    if (!files) {
        g_object_unref (enumerator);
        update_template_gallery_empty_state (self);
        return;
    }

    for (GList *l = files; l != NULL; l = l->next) {
        GFileInfo *info = G_FILE_INFO (l->data);
        const char *name = g_file_info_get_name (info);
        
        if (g_str_has_suffix (name, ".png") || g_str_has_suffix (name, ".jpg") || g_str_has_suffix (name, ".jpeg")) {
            GFile *child = g_file_enumerator_get_child (enumerator, info);
            char *full_path = g_file_get_path (child);
            add_file_to_gallery (self, full_path);
            g_free (full_path);
            g_object_unref (child);
        }
    }
    g_list_free_full (files, g_object_unref);
    g_file_enumerator_next_files_async (enumerator, 10, G_PRIORITY_DEFAULT, NULL, on_templates_enumerated, self);
}

static void on_templates_dir_opened (GObject *source_object, GAsyncResult *res, gpointer user_data) {
    GFile *dir = G_FILE (source_object);
    MemeWindow *self = MEME_WINDOW (user_data);
    GError *error = NULL;
    GFileEnumerator *enumerator = g_file_enumerate_children_finish (dir, res, &error);

    if (error) {
        g_clear_error (&error);
        return;
    }
    g_file_enumerator_next_files_async (enumerator, 10, G_PRIORITY_DEFAULT, NULL, on_templates_enumerated, self);
}

static void scan_directory_for_templates_async (MemeWindow *self, const char *dir_path) {
    GFile *dir = g_file_new_for_path (dir_path);
    g_file_enumerate_children_async (dir,
                                     "standard::name",
                                     G_FILE_QUERY_INFO_NONE,
                                     G_PRIORITY_DEFAULT,
                                     NULL,
                                     on_templates_dir_opened,
                                     self);
    g_object_unref (dir);
}

static gboolean is_template_hidden (MemeWindow *self, const char *path) {
    gchar **hidden = g_settings_get_strv (self->template_settings, "hidden-templates");
    gboolean found = FALSE;

    for (int i = 0; hidden[i] != NULL; i++) {
        if (g_strcmp0 (hidden[i], path) == 0) { found = TRUE; break; }
    }
    g_strfreev (hidden);
    return found;
}

static void update_restore_templates_sensitivity (MemeWindow *self) {
    gchar **hidden = g_settings_get_strv (self->template_settings, "hidden-templates");
    gtk_widget_set_sensitive (GTK_WIDGET (self->restore_templates_button), hidden[0] != NULL);
    g_strfreev (hidden);
}

static void hide_template (MemeWindow *self, const char *path) {
    gchar **hidden = g_settings_get_strv (self->template_settings, "hidden-templates");
    GPtrArray *updated = g_ptr_array_new ();
    gboolean already_hidden = FALSE;

    for (int i = 0; hidden[i] != NULL; i++) {
        g_ptr_array_add (updated, hidden[i]);
        if (g_strcmp0 (hidden[i], path) == 0) already_hidden = TRUE;
    }
    if (!already_hidden) g_ptr_array_add (updated, (gpointer) path);
    g_ptr_array_add (updated, NULL);

    g_settings_set_strv (self->template_settings, "hidden-templates",
                          (const gchar * const *) updated->pdata);

    g_ptr_array_free (updated, TRUE);
    g_strfreev (hidden);

    update_restore_templates_sensitivity (self);
}

static void on_restore_templates_clicked (MemeWindow *self) {
    g_settings_reset (self->template_settings, "hidden-templates");
    populate_template_gallery (self);
    update_restore_templates_sensitivity (self);
}

static void scan_resources_for_templates (MemeWindow *self) {
    GError *error = NULL;
    const char *res_path = "/io/github/vani_tty1/memerist/templates";
    char **files = g_resources_enumerate_children (res_path, 0, &error);

    if (files) {
        for (int i = 0; files[i] != NULL; i++) {
            char *full_uri = g_strdup_printf ("resource://%s/%s", res_path, files[i]);
            if (!is_template_hidden (self, full_uri))
                add_file_to_gallery (self, full_uri);
            g_free (full_uri);
        }
        g_strfreev (files);
    }
}

static void populate_template_gallery (MemeWindow *self) {
    char *user_dir;
    gtk_flow_box_remove_all(self->template_gallery);
    scan_resources_for_templates (self);
    user_dir = get_user_template_dir ();
    g_mkdir_with_parents (user_dir, 0755);
    scan_directory_for_templates_async (self, user_dir); 
    g_free (user_dir);
    update_template_gallery_empty_state (self);
}

static void on_template_selected (GtkFlowBox *flowbox, GtkFlowBoxChild *child, MemeWindow *self) {
    GtkWidget *image;
    const char *template_path;
    GError *error = NULL;

    if (self->template_select_mode) return;

    on_clear_clicked(self);

    if (!child) return;
  
    image = gtk_flow_box_child_get_child (child);
    template_path = g_object_get_data (G_OBJECT (image), "template-path");
    if (!template_path) return;

    g_clear_object (&self->template_image);
    if (self->layers) { meme_layer_list_free (self->layers); self->layers = NULL; }
    free_history_stack (&self->undo_stack); free_history_stack (&self->redo_stack);

    g_clear_pointer (&self->template_gif_path, g_free);
    self->template_is_gif = !g_str_has_prefix (template_path, "resource://") &&
                             g_str_has_suffix (template_path, ".gif");
    if (self->template_is_gif)
        self->template_gif_path = g_strdup (template_path);

    if (g_str_has_prefix (template_path, "resource://")) {
        self->template_image = gdk_pixbuf_new_from_resource (template_path + 11, &error);
    } else {
        self->template_image = gdk_pixbuf_new_from_file (template_path, &error);
    }

    if (self->template_image) {
        gtk_stack_set_visible_child_name (self->content_stack, "content");
        gtk_widget_set_sensitive (GTK_WIDGET (self->add_text_button), TRUE);
        gtk_widget_set_sensitive (GTK_WIDGET (self->export_button), TRUE);
        gtk_widget_set_sensitive (GTK_WIDGET (self->clear_button), TRUE);
        gtk_widget_set_sensitive (GTK_WIDGET (self->add_image_button), TRUE);
        gtk_widget_set_sensitive (GTK_WIDGET (self->deep_fry_button), TRUE);
        gtk_widget_set_sensitive (GTK_WIDGET (self->cinematic_button), TRUE);
        gtk_widget_set_sensitive (GTK_WIDGET (self->bw_button), TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(self->crop_mode_button), TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(self->save_project_button), TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(self->global_filters_button), TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(self->zoom_in), TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(self->zoom_out), TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(self->copy_clipboard_button), TRUE);
        self->zoom_level = 1.0;
        apply_zoom(self);
        render_meme (self);
        meme_window_start_gif_animation (self);
        adw_dialog_close (self->template_window);
    }
}

static void on_copy_import_finished(GObject *source_object, GAsyncResult *res, gpointer user_data){
    GFile * source_file = G_FILE(source_object);
    MemeWindow *self = MEME_WINDOW(g_object_get_data(G_OBJECT(source_file), "window-ptr"));
    char *dest_path = g_object_get_data(G_OBJECT(source_file), "dest-path");
    GError *error = NULL;
    
    if(g_file_copy_finish(source_file, res, &error)){
        add_file_to_gallery(self, dest_path);
        update_template_gallery_empty_state (self);
    }else{
        g_printerr("Error copying file: %s\n", error->message);
        g_clear_error(&error);
    }
    g_object_unref(source_file);
}

static void on_import_template_response(GObject *s, GAsyncResult *r, gpointer d){
    GtkFileDialog *dialog = GTK_FILE_DIALOG(s);
    MemeWindow *self = MEME_WINDOW(d);
    GFile *source_file, *dest_file;
    GError *error = NULL;
    char *filename, *user_dir_path, *dest_path;
    
    source_file = gtk_file_dialog_open_finish(dialog, r, &error);
    if(error){g_printerr("%s\n", error->message); g_error_free(error); return; }
    
    filename = g_file_get_basename(source_file);
    user_dir_path = get_user_template_dir();
    g_mkdir_with_parents(user_dir_path, 0755);
    dest_path = g_build_filename(user_dir_path, filename, NULL);
    dest_file = g_file_new_for_path(dest_path);
    
    g_object_set_data_full(G_OBJECT(source_file), "dest-path", g_strdup(dest_path), g_free);
    g_object_set_data(G_OBJECT(source_file), "window-ptr", self);
    g_file_copy_async(source_file, dest_file, G_FILE_COPY_OVERWRITE,G_PRIORITY_DEFAULT, NULL, NULL, NULL,on_copy_import_finished, NULL);
    g_free(filename); 
    g_free(user_dir_path); 
    g_free(dest_path);
    g_object_unref(dest_file);
}

static void on_import_template_clicked (MemeWindow *self) {
    GtkFileDialog *dialog = gtk_file_dialog_new ();
    gtk_file_dialog_set_title (dialog, "Import Template");
    gtk_file_dialog_open (dialog, GTK_WINDOW (self), NULL, on_import_template_response, self);
    g_object_unref (dialog);
}

static void on_delete_confirm_response (GObject *s, GAsyncResult *r, gpointer d) {
    AdwAlertDialog *dialog = ADW_ALERT_DIALOG(s);
    MemeWindow *self = MEME_WINDOW (d);
    const char *choice = adw_alert_dialog_choose_finish(dialog, r);
    if (g_strcmp0(choice, "delete") == 0) {
        GList *selected = gtk_flow_box_get_selected_children (self->template_gallery);
        GList *l;

        for (l = selected; l != NULL; l = l->next) {
            GtkFlowBoxChild *child = l->data;
            GtkWidget *image = gtk_flow_box_child_get_child (child);
            const char *path = g_object_get_data (G_OBJECT (image), "template-path");

            if (!path) continue;

            if (is_user_template (path)) {
                if (g_unlink (path) == 0)
                    gtk_flow_box_remove (self->template_gallery, GTK_WIDGET (child));
            } else {
                hide_template (self, path);
                gtk_flow_box_remove (self->template_gallery, GTK_WIDGET (child));
            }
        }
        g_list_free (selected);

        gtk_widget_set_sensitive (GTK_WIDGET (self->delete_template_button), FALSE);
        gtk_button_set_label (self->select_all_button, "Select All");
        update_template_gallery_empty_state (self);
    }
}

static void on_delete_template_clicked (MemeWindow *self) {
    GList *selected = gtk_flow_box_get_selected_children (self->template_gallery);
    guint count = g_list_length (selected);
    char *message;
    AdwAlertDialog *dialog;

    g_list_free (selected);
    if (count == 0) return;

    message = g_strdup_printf ("Delete %u selected template%s?", count, count == 1 ? "" : "s");
    dialog = ADW_ALERT_DIALOG (adw_alert_dialog_new (message, NULL));
    g_free (message);

    adw_alert_dialog_add_responses(dialog, "cancel", "Cancel", "delete", "Delete", NULL);
    adw_alert_dialog_set_response_appearance(dialog, "delete", ADW_RESPONSE_DESTRUCTIVE);
    adw_alert_dialog_choose(dialog, GTK_WIDGET (self->template_window), NULL, on_delete_confirm_response, self);
}

static guint count_flowbox_children (GtkFlowBox *flowbox) {
    guint total = 0;
    GtkWidget *child;

    for (child = gtk_widget_get_first_child (GTK_WIDGET (flowbox));
         child != NULL;
         child = gtk_widget_get_next_sibling (child)) {
        total++;
    }
    return total;
}

static void update_template_gallery_empty_state (MemeWindow *self) {
    gboolean has_templates = count_flowbox_children (self->template_gallery) > 0;
    gtk_stack_set_visible_child_name (self->template_content_stack,
                                       has_templates ? "gallery" : "empty");
    gtk_widget_set_sensitive (GTK_WIDGET (self->select_all_button), has_templates);
}

static void on_template_selection_changed (GtkFlowBox *flowbox, MemeWindow *self) {
    GList *selected;
    guint selected_count, total;

    if (!self->template_select_mode) return;

    selected = gtk_flow_box_get_selected_children (flowbox);
    selected_count = g_list_length (selected);
    g_list_free (selected);

    total = count_flowbox_children (flowbox);

    gtk_widget_set_sensitive (GTK_WIDGET (self->delete_template_button), selected_count > 0);
    gtk_button_set_label (self->select_all_button, (total > 0 && selected_count >= total) ? "Deselect All" : "Select All");
}

static void on_select_all_clicked (MemeWindow *self) {
    guint total = count_flowbox_children (self->template_gallery);
    GList *selected = gtk_flow_box_get_selected_children (self->template_gallery);
    guint selected_count = g_list_length (selected);
    g_list_free (selected);

    if (total > 0 && selected_count >= total) {
        gtk_flow_box_unselect_all (self->template_gallery);
    } else {
        gtk_flow_box_select_all (self->template_gallery);
    }
}

static void set_template_select_mode (MemeWindow *self, gboolean active) {
    self->template_select_mode = active;

    gtk_flow_box_unselect_all (self->template_gallery);
    gtk_flow_box_set_selection_mode (self->template_gallery,
                                      active ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE);

    gtk_widget_set_visible (GTK_WIDGET (self->import_template_button), !active);
    gtk_widget_set_visible (GTK_WIDGET (self->select_all_button), active);
    gtk_widget_set_visible (GTK_WIDGET (self->delete_template_button), active);
    gtk_widget_set_sensitive (GTK_WIDGET (self->delete_template_button), FALSE);
    gtk_button_set_label (self->select_all_button, "Select All");

    if (active) {
        gtk_button_set_label (self->select_mode_button, "Cancel");
        gtk_widget_set_tooltip_text (GTK_WIDGET (self->select_mode_button), "Cancel");
    } else {
        gtk_button_set_icon_name (self->select_mode_button, "document-edit-symbolic");
        gtk_widget_set_tooltip_text (GTK_WIDGET (self->select_mode_button), "Manage Templates");
    }
}

static void on_select_mode_clicked (MemeWindow *self) {
    set_template_select_mode (self, !self->template_select_mode);
}

void apply_zoom(MemeWindow *self) {
    int img_w, img_h, win_w, win_h;
    double fit_scale, final_scale;

    if (!self->template_image) return;
    img_w = gdk_pixbuf_get_width(self->template_image);
    img_h = gdk_pixbuf_get_height(self->template_image);
    win_w = gtk_widget_get_width(GTK_WIDGET(self)) - 320;
    win_h = gtk_widget_get_height(GTK_WIDGET(self)) - 60;

    fit_scale = MIN((double)win_w / img_w, (double)win_h / img_h) * 0.6;
    final_scale = fit_scale * self->zoom_level;

    gtk_widget_set_size_request(GTK_WIDGET(self->meme_preview), (int)(img_w * final_scale), (int)(img_h * final_scale));
}

static void on_zoom_in_clicked(MemeWindow *self) {
    self->zoom_level += 0.2; 
    apply_zoom(self);
}

static void on_zoom_out_clicked(MemeWindow *self) {
    self->zoom_level = MAX(0.2, self->zoom_level - 0.2); 
    apply_zoom(self);
}

static gboolean on_canvas_scroll(GtkEventControllerScroll *ctrl, double dx, double dy, MemeWindow *self) {
    if (dy > 0) {
        self->zoom_level = MAX(0.2, self->zoom_level - 0.1);
    } else if (dy < 0) {
        self->zoom_level += 0.1;
    }
    apply_zoom(self);
    return TRUE;
}

static gint sort_templates_by_mtime(GtkFlowBoxChild *child1, GtkFlowBoxChild *child2, gpointer user_data) {
    GtkWidget *pic1 = gtk_flow_box_child_get_child (child1);
    GtkWidget *pic2 = gtk_flow_box_child_get_child (child2);

    gint64 *mtime1 = g_object_get_data (G_OBJECT (pic1), "mtime");
    gint64 *mtime2 = g_object_get_data (G_OBJECT (pic2), "mtime");

    gint64 t1 = mtime1 ? *mtime1 : 0;
    gint64 t2 = mtime2 ? *mtime2 : 0;

    if (t1 > t2) return -1;
    if (t1 < t2) return 1;
    return 0;
}

#define MEME_TYPE_THEME_SWITCHER (meme_theme_switcher_get_type())
G_DECLARE_FINAL_TYPE (MemeThemeSwitcher, meme_theme_switcher, MEME, THEME_SWITCHER, AdwBin)

struct _MemeThemeSwitcher {
    AdwBin parent_instance;
};

G_DEFINE_FINAL_TYPE (MemeThemeSwitcher, meme_theme_switcher, ADW_TYPE_BIN)

static void meme_theme_switcher_class_init (MemeThemeSwitcherClass *klass) {
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    gtk_widget_class_set_template_from_resource (widget_class, "/io/github/vani_tty1/memerist/theme-switcher.ui");
}

static void meme_theme_switcher_init (MemeThemeSwitcher *self) {
    gtk_widget_init_template (GTK_WIDGET (self));
}

static void
meme_window_toggle_sidebar (GtkWidget  *widget,
                             const char *action_name,
                             GVariant   *parameter) {
    MemeWindow *self = MEME_WINDOW (widget);
    gboolean visible = adw_overlay_split_view_get_show_sidebar (self->split_view);
    adw_overlay_split_view_set_show_sidebar (self->split_view, !visible);
}

static void meme_window_class_init (MemeWindowClass *klass) {
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    
    object_class->finalize = myapp_window_finalize;
    g_type_ensure (MEME_TYPE_THEME_SWITCHER);
    gtk_widget_class_set_template_from_resource (widget_class, "/io/github/vani_tty1/memerist/meme-window.ui");
    gtk_widget_class_bind_template_child(widget_class, MemeWindow, export_loading_screen);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_group);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, open_template_row);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, transform_group);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, meme_preview);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, crop_overlay_area);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, content_stack);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, split_view);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, add_text_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, font_choose_row);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, font_choose_btn);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_text_container);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_text_view);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_font_size);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_font_size_row);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, export_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, load_image_button);
    gtk_widget_class_bind_template_child(widget_class, MemeWindow, pill_btn_open_image);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, clear_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, add_image_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, global_filters_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, deep_fry_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, cinematic_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, bw_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_opacity_scale);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, layer_rotation_scale);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, blend_mode_row);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, delete_layer_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, crop_mode_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, rotate_left_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, rotate_right_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, flip_h_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, flip_v_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, crop_square_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, crop_43_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, crop_169_button);
    gtk_widget_class_bind_template_callback (widget_class, on_apply_crop_clicked);
    gtk_widget_class_bind_template_callback (widget_class, on_cancel_crop_clicked);
    gtk_widget_class_bind_template_callback (widget_class, on_open_template_window_clicked);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, save_project_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, load_project_button);
    gtk_widget_class_bind_template_child(widget_class, MemeWindow, main_menu_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, zoom_in);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, zoom_out);
    gtk_widget_class_bind_template_child(widget_class, MemeWindow, copy_clipboard_button);
    gtk_widget_class_bind_template_child(widget_class, MemeWindow, copy_clip_feedback);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, text_color_btn);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, stroke_color_btn);
    gtk_widget_class_bind_template_child(widget_class, MemeWindow, file_popover);

    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_add_image_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_crop_mode_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_add_text_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_copy_clipboard_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_global_filters_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_cinematic_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_deep_fry_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_delete_layer_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_zoom_in);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_zoom_out);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_clear_button);

    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_rotate_left_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_rotate_right_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_flip_h_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_flip_v_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_crop_square_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_crop_43_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_crop_169_button);

    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_tools_page);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_transform_page);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_text_page);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_font_choose_btn);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_text_color_btn);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_stroke_color_btn);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_layer_text_container);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_layer_text_view);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_layer_font_size);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_text_delete_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_exit_text_button);
    gtk_widget_class_bind_template_child (widget_class, MemeWindow, footer_bw_button);
    gtk_widget_class_install_action (widget_class, "win.toggle-sidebar", NULL,
                                      meme_window_toggle_sidebar);
    gtk_widget_class_add_binding_action (widget_class, GDK_KEY_F9, 0,
                                          "win.toggle-sidebar", NULL);
}

static void meme_window_init (MemeWindow *self) {
    GtkEventController *scroll;
    GtkEventController *key_controller;
    GtkEventController *motion;
    GtkTextBuffer *buffer;

    gtk_widget_init_template (GTK_WIDGET (self));
    #ifdef PROFILE
        if (g_strcmp0 (PROFILE, "development") == 0)
            gtk_widget_add_css_class (GTK_WIDGET (self), "devel");
    #endif
    self->layers = NULL; self->undo_stack = NULL; self->redo_stack = NULL;
    g_signal_connect (self->text_color_btn, "notify::rgba", G_CALLBACK (on_color_changed), self);
    g_signal_connect (self->stroke_color_btn, "notify::rgba", G_CALLBACK (on_color_changed), self);
    
    g_signal_connect (self->rotate_left_button, "clicked", G_CALLBACK (on_rotate_clicked), self);
    g_signal_connect (self->rotate_right_button, "clicked", G_CALLBACK (on_rotate_clicked), self);
    g_signal_connect (self->flip_h_button, "clicked", G_CALLBACK (on_flip_clicked), self);
    g_signal_connect (self->flip_v_button, "clicked", G_CALLBACK (on_flip_clicked), self);
    g_signal_connect (self->crop_square_button, "clicked", G_CALLBACK (on_crop_preset_clicked), self);
    g_signal_connect (self->crop_43_button, "clicked", G_CALLBACK (on_crop_preset_clicked), self);
    g_signal_connect (self->crop_169_button, "clicked", G_CALLBACK (on_crop_preset_clicked), self);
    g_signal_connect (self->crop_mode_button, "toggled", G_CALLBACK (on_crop_mode_toggled), self);
    
    g_signal_connect_swapped (self->add_text_button, "clicked", G_CALLBACK (on_add_text_clicked), self);
    g_signal_connect (self->font_choose_btn, "notify::font-desc", G_CALLBACK (on_font_changed), self);
    buffer = gtk_text_view_get_buffer (self->layer_text_view);
    g_signal_connect_swapped (buffer, "changed", G_CALLBACK (on_layer_text_changed), self);
    g_signal_connect_swapped (self->layer_font_size, "value-changed", G_CALLBACK (on_layer_text_changed), self);
    
    g_signal_connect_swapped (self->load_image_button, "clicked", G_CALLBACK (on_load_image_clicked), self);
    g_signal_connect_swapped (self->pill_btn_open_image, "clicked", G_CALLBACK(on_load_image_clicked), self);
    g_signal_connect_swapped (self->clear_button, "clicked", G_CALLBACK (on_clear_clicked), self);
    g_signal_connect_swapped (self->add_image_button, "clicked", G_CALLBACK (on_add_image_clicked), self);
    g_signal_connect_swapped (self->export_button, "clicked", G_CALLBACK (on_export_clicked), self);
    g_signal_connect_swapped (self->save_project_button, "clicked", G_CALLBACK (myapp_window_save_project), self);
    g_signal_connect_swapped (self->load_project_button, "clicked", G_CALLBACK (on_load_project_clicked), self);
    g_signal_connect_swapped(self->copy_clipboard_button, "clicked", G_CALLBACK(on_copy_clipboard_clicked), self);
    
    {
        GtkBuilder *template_builder = gtk_builder_new_from_resource ("/io/github/vani_tty1/memerist/template-window.ui");
        self->template_window = ADW_DIALOG (gtk_builder_get_object (template_builder, "template_window"));
        g_object_ref_sink (self->template_window);
        self->template_gallery = GTK_FLOW_BOX (gtk_builder_get_object (template_builder, "template_gallery"));
        self->import_template_button = GTK_BUTTON (gtk_builder_get_object (template_builder, "import_template_button"));
        self->delete_template_button = GTK_BUTTON (gtk_builder_get_object (template_builder, "delete_template_button"));
        self->select_mode_button = GTK_BUTTON (gtk_builder_get_object (template_builder, "select_mode_button"));
        self->select_all_button = GTK_BUTTON (gtk_builder_get_object (template_builder, "select_all_button"));
        self->restore_templates_button = GTK_BUTTON (gtk_builder_get_object (template_builder, "restore_templates_button"));
        self->template_content_stack = GTK_STACK (gtk_builder_get_object (template_builder, "template_content_stack"));
        g_object_unref (template_builder);
    }

    self->template_settings = g_settings_new ("io.github.vani_tty1.memerist");
    update_restore_templates_sensitivity (self);

    g_signal_connect_swapped (self->import_template_button, "clicked", G_CALLBACK (on_import_template_clicked), self);
    g_signal_connect_swapped (self->delete_template_button, "clicked", G_CALLBACK (on_delete_template_clicked), self);
    g_signal_connect_swapped (self->select_all_button, "clicked", G_CALLBACK (on_select_all_clicked), self);
    g_signal_connect_swapped (self->restore_templates_button, "clicked", G_CALLBACK (on_restore_templates_clicked), self);
    g_signal_connect_swapped (self->select_mode_button, "clicked", G_CALLBACK (on_select_mode_clicked), self);
    g_signal_connect (self->template_gallery, "child-activated", G_CALLBACK (on_template_selected), self);
    g_signal_connect (self->template_gallery, "selected-children-changed", G_CALLBACK (on_template_selection_changed), self);
    
    g_signal_connect (self->deep_fry_button, "toggled", G_CALLBACK (on_deep_fry_toggled), self);
    g_signal_connect_swapped (self->cinematic_button, "toggled", G_CALLBACK (on_text_changed), self);
    
    g_signal_connect_swapped (self->layer_opacity_scale, "value-changed", G_CALLBACK (on_layer_control_changed), self);
    g_signal_connect_swapped (self->layer_rotation_scale, "value-changed", G_CALLBACK (on_layer_control_changed), self);
    g_signal_connect_swapped (self->blend_mode_row, "notify::selected", G_CALLBACK (on_layer_control_changed), self);
    g_signal_connect_swapped (self->delete_layer_button, "clicked", G_CALLBACK (on_delete_layer_clicked), self);
    
    gtk_drawing_area_set_draw_func (self->crop_overlay_area, draw_crop_overlay, self, NULL);
    gtk_widget_set_can_target (GTK_WIDGET (self->crop_overlay_area), FALSE);

    // Handlers moved to meme-canvas.c
    self->drag_gesture = GTK_GESTURE_DRAG (gtk_gesture_drag_new ());
    gtk_widget_add_controller (GTK_WIDGET (self->meme_preview), GTK_EVENT_CONTROLLER (self->drag_gesture));
    g_signal_connect (self->drag_gesture, "drag-begin", G_CALLBACK (on_drag_begin), self);
    g_signal_connect (self->drag_gesture, "drag-update", G_CALLBACK (on_drag_update), self);
    g_signal_connect (self->drag_gesture, "drag-end", G_CALLBACK (on_drag_end), self);
    
    self->zoom_level = 1.0;
    g_signal_connect_swapped (self->zoom_in, "clicked", G_CALLBACK (on_zoom_in_clicked), self);
    g_signal_connect_swapped (self->zoom_out, "clicked", G_CALLBACK (on_zoom_out_clicked), self);


    g_signal_connect_swapped (self->footer_add_image_button, "clicked", G_CALLBACK (on_add_image_clicked), self);
    g_signal_connect (self->footer_crop_mode_button, "toggled", G_CALLBACK (on_crop_mode_toggled), self);
    g_signal_connect_swapped (self->footer_add_text_button, "clicked", G_CALLBACK (on_add_text_clicked), self);
    g_signal_connect_swapped (self->footer_copy_clipboard_button, "clicked", G_CALLBACK (on_copy_clipboard_clicked), self);
    g_signal_connect (self->footer_deep_fry_button, "toggled", G_CALLBACK (on_deep_fry_toggled), self);
    g_signal_connect_swapped (self->footer_cinematic_button, "toggled", G_CALLBACK (on_text_changed), self);
    g_signal_connect_swapped (self->footer_delete_layer_button, "clicked", G_CALLBACK (on_delete_layer_clicked), self);
    g_signal_connect_swapped (self->footer_zoom_in, "clicked", G_CALLBACK (on_zoom_in_clicked), self);
    g_signal_connect_swapped (self->footer_zoom_out, "clicked", G_CALLBACK (on_zoom_out_clicked), self);
    g_signal_connect_swapped (self->footer_clear_button, "clicked", G_CALLBACK (on_clear_clicked), self);

    g_signal_connect (self->footer_rotate_left_button, "clicked", G_CALLBACK (on_rotate_clicked), self);
    g_signal_connect (self->footer_rotate_right_button, "clicked", G_CALLBACK (on_rotate_clicked), self);
    g_signal_connect (self->footer_flip_h_button, "clicked", G_CALLBACK (on_flip_clicked), self);
    g_signal_connect (self->footer_flip_v_button, "clicked", G_CALLBACK (on_flip_clicked), self);
    g_signal_connect (self->footer_crop_square_button, "clicked", G_CALLBACK (on_crop_preset_clicked), self);
    g_signal_connect (self->footer_crop_43_button, "clicked", G_CALLBACK (on_crop_preset_clicked), self);
    g_signal_connect (self->footer_crop_169_button, "clicked", G_CALLBACK (on_crop_preset_clicked), self);

    g_signal_connect_swapped (self->footer_text_delete_button, "clicked", G_CALLBACK (on_delete_layer_clicked), self);
    g_signal_connect_swapped (self->footer_exit_text_button, "clicked", G_CALLBACK (on_exit_text_editing_clicked), self);
    g_signal_connect (self->bw_button, "toggled", G_CALLBACK (on_deep_fry_toggled), self);
    g_signal_connect (self->footer_bw_button, "toggled", G_CALLBACK (on_deep_fry_toggled), self);
    populate_template_gallery (self);
    
    motion = gtk_event_controller_motion_new ();
    gtk_widget_add_controller (GTK_WIDGET (self->meme_preview), motion);
    g_signal_connect (motion, "motion", G_CALLBACK (on_mouse_move), self);
    
    key_controller = gtk_event_controller_key_new ();
    g_signal_connect (key_controller, "key-pressed", G_CALLBACK (on_window_key_pressed), self);
    gtk_widget_add_controller (GTK_WIDGET (self), key_controller);
    
    
    scroll = gtk_event_controller_scroll_new(GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
    g_signal_connect(scroll, "scroll", G_CALLBACK(on_canvas_scroll), self);
    gtk_widget_add_controller(GTK_WIDGET(self->meme_preview), scroll);

    gtk_flow_box_set_sort_func (self->template_gallery, sort_templates_by_mtime, NULL, NULL);
    update_footer_pages (self);
}
