/*
 * exm-detail-view.c
 *
 * Copyright 2022-2025 Matthew Jakeman <mjakeman26@outlook.co.nz>
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "exm-detail-view.h"

#include "exm-comment-dialog.h"
#include "exm-comment-tile.h"
#include "exm-config.h"
#include "exm-enums.h"
#include "exm-info-bar.h"
#include "exm-install-button.h"
#include "exm-screenshot.h"
#include "exm-screenshot-view.h"
#include "exm-types.h"
#include "local/exm-manager.h"
#include "web/exm-comment-provider.h"
#include "web/exm-data-provider.h"
#include "web/exm-image-resolver.h"
#include "web/exm-versions-provider.h"
#include "web/model/exm-version-result.h"
#include "web/model/exm-shell-versions.h"
#include "web/model/exm-comment.h"

#include <glib/gi18n.h>

struct _ExmDetailView
{
    AdwNavigationPage parent_instance;

    ExmManager *manager;
    ExmDataProvider *provider;
    ExmImageResolver *resolver;
    ExmVersionsProvider *versions_provider;
    ExmCommentProvider *comment_provider;
    GCancellable *resolver_cancel;

    GdkPaintable *screenshot;
    gchar *shell_version;
    gchar *uuid;

    AdwHeaderBar *header_bar;
    AdwWindowTitle *title;
    GtkStack *stack;
    GtkLabel *error_label;
    AdwStatusPage *error_status;
    GtkButton *ext_install;
    GtkLabel *ext_description;
    GtkImage *ext_icon;
    GtkLabel *ext_title;
    GtkLabel *ext_author;
    ExmScreenshot *ext_screenshot;
    GtkOverlay *ext_screenshot_container;
    GtkButton *ext_screenshot_popout_button;
    ExmInfoBar *ext_info_bar;
    GtkScrolledWindow *scroll_area;
    GtkStack *comment_stack;
    AdwWrapBox *comment_box;
    GtkButton *show_more_btn;

    AdwActionRow *link_homepage;
    gchar *uri_homepage;
    AdwExpanderRow *links_donations;
    gchar **uri_donations;
    GList *donation_rows_list;
    AdwActionRow *link_extensions;
    gchar *uri_extensions;
    guint id;
    guint comments_signal_id;
    guint install_signal_id;
};

G_DEFINE_FINAL_TYPE (ExmDetailView, exm_detail_view, ADW_TYPE_NAVIGATION_PAGE)

enum {
    PROP_0,
    PROP_MANAGER,
    PROP_SHELL_VERSION,
    PROP_SCREENSHOT,
    N_PROPS
};

static GParamSpec *properties [N_PROPS];

ExmDetailView *
exm_detail_view_new (void)
{
    return g_object_new (EXM_TYPE_DETAIL_VIEW, NULL);
}

static void
exm_detail_view_get_property (GObject    *object,
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
    ExmDetailView *self = EXM_DETAIL_VIEW (object);

    switch (prop_id)
    {
    case PROP_MANAGER:
        g_value_set_object (value, self->manager);
        break;
    case PROP_SHELL_VERSION:
        g_value_set_string (value, self->shell_version);
        break;
    case PROP_SCREENSHOT:
        g_value_set_object (value, self->screenshot);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void on_bind_manager (ExmDetailView *self);

static void
exm_detail_view_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
    ExmDetailView *self = EXM_DETAIL_VIEW (object);

    switch (prop_id)
    {
    case PROP_MANAGER:
        self->manager = g_value_get_object (value);
        on_bind_manager (self);
        break;
    case PROP_SHELL_VERSION:
        self->shell_version = g_value_dup_string (value);
        break;
    case PROP_SCREENSHOT:
        self->screenshot = g_value_get_object (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
on_icon_loaded (GObject       *source,
                GAsyncResult  *res,
                ExmDetailView *self)
{
    GError *error = NULL;
    GdkTexture *texture = exm_image_resolver_resolve_finish (EXM_IMAGE_RESOLVER (source),
                                                             res, &error);

    if (error)
    {
        // TODO: Properly log this
        g_critical ("%s\n", error->message);
        return;
    }

    gtk_image_set_from_paintable (self->ext_icon, GDK_PAINTABLE (texture));
    g_object_unref (texture);
    g_object_unref (self);
}

static void
on_image_loaded (GObject       *source,
                 GAsyncResult  *res,
                 ExmDetailView *self)
{
    GError *error = NULL;
    GdkTexture *texture = exm_image_resolver_resolve_finish (EXM_IMAGE_RESOLVER (source),
                                                             res, &error);

    if (error)
    {
        // TODO: Properly log this
        g_critical ("%s\n", error->message);
        return;
    }

    g_object_set (self, "screenshot", GDK_PAINTABLE (texture), NULL);
    exm_screenshot_set_paintable (self->ext_screenshot, GDK_PAINTABLE (texture));
    exm_screenshot_display (self->ext_screenshot);
    g_object_unref (texture);
    g_object_unref (self);

    gtk_widget_set_visible (GTK_WIDGET (self->ext_screenshot_popout_button), TRUE);
}

static void
queue_resolve_image (ExmDetailView    *self,
                     const gchar      *image_uri,
                     GCancellable     *cancellable,
                     gboolean          is_icon)
{
    exm_image_resolver_resolve_async (self->resolver, image_uri, cancellable,
                                      (GAsyncReadyCallback) (is_icon ? on_icon_loaded : on_image_loaded),
                                      g_object_ref (self));
}

static void
delete_comment_tiles (ExmDetailView *self)
{
  GtkWidget *child;

  while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->comment_box))) != NULL)
    adw_wrap_box_remove (self->comment_box, child);
}

static void
on_get_comments (GObject       *source,
                 GAsyncResult  *res,
                 ExmDetailView *self)
{
    GError *error = NULL;

    GListModel *model = exm_comment_provider_get_comments_finish (EXM_COMMENT_PROVIDER (source), res, &error);

    if (error != NULL)
    {
        if (error->domain == g_quark_try_string ("request-error-quark") && error->code == 403)
        {
            gtk_stack_set_visible_child_name (self->comment_stack, "page_disabled");
        }
        else
        {
            gtk_stack_set_visible_child_name (self->comment_stack, "page_error");
            gtk_label_set_label (self->error_label, error->message);
        }

        return;
    }

    guint n_comments = g_list_model_get_n_items (model);

    if (!n_comments)
    {
        gtk_stack_set_visible_child_name (self->comment_stack, "page_empty");
    }
    else
    {
        gtk_widget_set_visible (GTK_WIDGET (self->show_more_btn), n_comments >= 5);
        gtk_stack_set_visible_child_name (self->comment_stack, "page_comments");
    }

    delete_comment_tiles (self);

    for (guint i = 0; i < g_list_model_get_n_items (model); i++)
    {
        ExmComment *comment = g_list_model_get_item (model, i);
        GtkWidget *tile = GTK_WIDGET (exm_comment_tile_new (comment));
        gtk_widget_add_css_class (tile, "card");
        adw_wrap_box_append (self->comment_box, tile);
        g_object_unref (comment);
    }
}

static void
queue_resolve_comments (ExmDetailView *self,
                        gint           pk,
                        GCancellable  *cancellable)
{
    gtk_stack_set_visible_child_name (self->comment_stack, "page_spinner");
    exm_comment_provider_get_comments_async (self->comment_provider, pk, false, cancellable,
                                             (GAsyncReadyCallback) on_get_comments,
                                             self);
}

static void
show_more_comments (GtkButton     *button G_GNUC_UNUSED,
                    ExmDetailView *self)
{
    GtkRoot *toplevel;
    ExmCommentDialog *dlg;

    dlg = exm_comment_dialog_new (self->id);
    toplevel = gtk_widget_get_root (GTK_WIDGET (self));

    adw_dialog_present (ADW_DIALOG (dlg), GTK_WIDGET (toplevel));
}

static void
on_install_status (ExmManager            *manager G_GNUC_UNUSED,
                   ExmInstallButtonState  state,
                   ExmDetailView         *self)
{
    g_object_set (self->ext_install, "state", state, NULL);
}

static void
install_remote (GtkButton     *button,
                ExmDetailView *self)
{
    gboolean warn;
    ExmInstallButtonState state;

    g_object_get (self->ext_install, "state", &state, NULL);

    warn = (state == EXM_INSTALL_BUTTON_STATE_UNSUPPORTED);

    g_object_set (self->ext_install, "state", EXM_INSTALL_BUTTON_STATE_INSTALLING, NULL);

    // Move focus to previous widget to avoid losing it after the button becomes insensitive
    gtk_widget_grab_focus (GTK_WIDGET (self->ext_author));

    gtk_widget_activate_action (GTK_WIDGET (button),
                                "ext.install",
                                "(sb)", self->uuid, warn);
}

static void
new_donation_row (ExmDetailView *self,
                  int            num_donation)
{
    GtkWidget *row;
    GtkWidget *external_link_icon;

    row = adw_action_row_new ();

    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), self->uri_donations[num_donation]);
    gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);

    gtk_actionable_set_action_name (GTK_ACTIONABLE (row), "detail.open-donation");
    gtk_actionable_set_action_target_value (GTK_ACTIONABLE (row), g_variant_new_int32 (num_donation));

    external_link_icon = gtk_image_new_from_icon_name ("external-link-symbolic");
    adw_action_row_add_suffix (ADW_ACTION_ROW (row), external_link_icon);

    adw_expander_row_add_row (self->links_donations, row);

    self->donation_rows_list = g_list_append (self->donation_rows_list, row);
}

static void
delete_donation_rows (ExmDetailView *self)
{
    for (GList *iter = self->donation_rows_list; iter != NULL; iter = g_list_next (iter))
        adw_expander_row_remove (self->links_donations, GTK_WIDGET (iter->data));

    g_list_free (self->donation_rows_list);
    self->donation_rows_list = NULL;
}

static void
update_donation_rows (ExmDetailView  *self,
                      gchar         **donation_urls)
{
    gtk_widget_set_visible (GTK_WIDGET (self->links_donations), FALSE);
    delete_donation_rows (self);

    if (donation_urls == NULL || donation_urls[0] == NULL)
        return;

    self->uri_donations = g_new0 (gchar *, g_strv_length (donation_urls));

    for (int i = 0; donation_urls[i] != NULL; i++)
    {
        self->uri_donations[i] = g_uri_resolve_relative (donation_urls[i],
                                                         "",
                                                         G_URI_FLAGS_NONE,
                                                         NULL);
        new_donation_row (self, i);
    }

    adw_expander_row_set_expanded (self->links_donations, FALSE);
    gtk_widget_set_visible (GTK_WIDGET (self->links_donations), TRUE);
}

static void
on_version_loaded (GObject      *source,
                   GAsyncResult *result,
                   gpointer      user_data)
{
    gchar *next;
    GError *error = NULL;
    GListModel *list;
    ExmDetailView *self;
    gboolean is_installed;
    ExmInstallButtonState install_state;
    ExmVersionResult *compatible_version = NULL;

    list = exm_versions_provider_query_finish (EXM_VERSIONS_PROVIDER (source), result, &next, &error);
    self = EXM_DETAIL_VIEW (user_data);

    is_installed = exm_manager_is_installed_uuid (self->manager, self->uuid);

    if (error)
    {
        install_state = is_installed ? EXM_INSTALL_BUTTON_STATE_INSTALLED
                                     : EXM_INSTALL_BUTTON_STATE_UNSUPPORTED;

        g_object_set (self->ext_install, "state", install_state, NULL);
        g_object_set (self->ext_info_bar, "version", _("Unsupported"), NULL);

        g_clear_error (&error);
        return;
    }

    const gchar *shell_version;
    g_object_get (self->versions_provider, "shell-version", &shell_version, NULL);

    for (guint i = 0; i < g_list_model_get_n_items (list); i++)
    {
        ExmVersionResult *item = EXM_VERSION_RESULT (g_list_model_get_object (list, i));

        if (exm_version_result_supports_shell_version (item, shell_version))
        {
            if (compatible_version)
                g_object_unref (compatible_version);
            compatible_version = item;
        }
        else
        {
            g_object_unref (item);
        }
    }

    if (compatible_version)
    {
        gint version;
        gchar *version_name = NULL;
        gchar **session_modes;

        g_object_get (compatible_version,
                      "version", &version,
                      "version_name", &version_name,
                      "session_modes", &session_modes,
                      NULL);

        install_state = is_installed ? EXM_INSTALL_BUTTON_STATE_INSTALLED
                                     : EXM_INSTALL_BUTTON_STATE_DEFAULT;

        g_object_set (self->ext_install, "state", install_state, NULL);
        g_object_set (self->ext_info_bar, "version", version_name ? version_name
                                                                  : g_strdup_printf("%d", version),
                      "session-modes", session_modes, NULL);

        g_free (version_name);
        g_object_unref (compatible_version);
    }
    else if (next != NULL)
    {
        exm_versions_provider_query_next_async (self->versions_provider,
                                                next,
                                                self->resolver_cancel,
                                                on_version_loaded,
                                                self);
        g_object_unref (list);
        return;
    }
    else
    {
        install_state = is_installed ? EXM_INSTALL_BUTTON_STATE_INSTALLED
                                     : EXM_INSTALL_BUTTON_STATE_UNSUPPORTED;

        g_object_set (self->ext_install, "state", install_state, NULL);
        g_object_set (self->ext_info_bar, "version", _("Unsupported"),
                      "session-modes", NULL, NULL);
    }

    g_object_unref (list);
}


static void
on_data_loaded (GObject      *source,
                GAsyncResult *result,
                gpointer      user_data)
{
    ExmSearchResult *data;
    GError *error = NULL;
    ExmDetailView *self;

    data = exm_data_provider_get_finish (EXM_DATA_PROVIDER (source), result, &error);
    self = EXM_DETAIL_VIEW (user_data);

    if (error)
    {
        if (error->domain == g_quark_try_string ("request-error-quark") && error->code == 404)
        {
            gtk_stack_set_visible_child_name (self->stack, "page_empty");
        }
        else
        {
            adw_status_page_set_description (self->error_status, error->message);
            gtk_stack_set_visible_child_name (self->stack, "page_error");
        }

        g_clear_error (&error);

        return;
    }

    if (EXM_IS_SEARCH_RESULT (data))
    {
        guint id, downloads;
        gchar *uuid, *name, *creator, *description, *screenshot_uri, *icon_uri, *url, *link;
        gchar **donation_urls;
        g_object_get (data,
                      "id", &id,
                      "uuid", &uuid,
                      "name", &name,
                      "creator", &creator,
                      "description", &description,
                      "downloads", &downloads,
                      "screenshot", &screenshot_uri,
                      "icon", &icon_uri,
                      "url", &url,
                      "donation_urls", &donation_urls,
                      "link", &link,
                      NULL);

        adw_window_title_set_title (self->title, name);
        adw_window_title_set_subtitle (self->title, uuid);
        adw_navigation_page_set_title (ADW_NAVIGATION_PAGE (self), name);

        gtk_image_set_from_icon_name (self->ext_icon, "puzzle-piece-symbolic");
        gtk_widget_set_tooltip_text (GTK_WIDGET (self->ext_icon),
                                     // Translators: '%s' = extension name
                                     g_strdup_printf (_("%s Icon"), name));
        gtk_label_set_label (self->ext_title, name);
        gtk_label_set_label (self->ext_author, creator);
        gtk_label_set_label (self->ext_description, description);
        g_object_set (self->ext_info_bar, "downloads", downloads, NULL);

        if (self->resolver_cancel)
        {
            g_cancellable_cancel (self->resolver_cancel);
            g_clear_object (&self->resolver_cancel);
        }

        if (icon_uri != NULL)
        {
            self->resolver_cancel = g_cancellable_new ();

            queue_resolve_image (self, icon_uri, self->resolver_cancel, TRUE);
        }

        if (screenshot_uri != NULL)
        {
            self->resolver_cancel = g_cancellable_new ();

            exm_screenshot_set_paintable (self->ext_screenshot, NULL);
            exm_screenshot_reset (self->ext_screenshot);

            gtk_widget_set_visible (GTK_WIDGET (self->ext_screenshot_container), TRUE);
            gtk_widget_set_visible (GTK_WIDGET (self->ext_screenshot_popout_button), FALSE);

            queue_resolve_image (self, screenshot_uri, self->resolver_cancel, FALSE);

            gtk_widget_set_tooltip_text (GTK_WIDGET (self->ext_screenshot),
                                         // Translators: '%s' = extension name
                                         g_strdup_printf (_("%s Screenshot"), name));
        }
        else
        {
            gtk_widget_set_visible (GTK_WIDGET (self->ext_screenshot_container), FALSE);
        }

        exm_versions_provider_query_async (self->versions_provider, uuid, self->resolver_cancel, on_version_loaded, self);

        self->uri_homepage = g_uri_resolve_relative (url,
                                                     "",
                                                     G_URI_FLAGS_NONE,
                                                     NULL);

        update_donation_rows (self, donation_urls);

        self->uri_extensions = g_uri_resolve_relative ("https://extensions.gnome.org/",
                                                       link,
                                                       G_URI_FLAGS_NONE,
                                                       NULL);

        adw_action_row_set_subtitle (self->link_homepage, self->uri_homepage);
        adw_action_row_set_subtitle (self->link_extensions, self->uri_extensions);

        self->id = id;

        if (self->comments_signal_id > 0)
            g_signal_handler_disconnect (self->show_more_btn, self->comments_signal_id);

        self->comments_signal_id = g_signal_connect (self->show_more_btn,
                                                     "clicked",
                                                     G_CALLBACK (show_more_comments),
                                                     self);

        queue_resolve_comments (self, id, self->resolver_cancel);

        // Reset focus and scroll position
        gtk_widget_grab_focus (GTK_WIDGET (self->ext_icon));
        gtk_adjustment_set_value (gtk_scrolled_window_get_vadjustment (self->scroll_area), 0);

        gtk_stack_set_visible_child_name (self->stack, "page_detail");

        return;
    }
}

void
exm_detail_view_load_for_uuid (ExmDetailView *self,
                               gchar         *uuid)
{
    self->uuid = uuid;

    adw_window_title_set_title (self->title, NULL);
    adw_window_title_set_subtitle (self->title, NULL);
    g_object_set (self->ext_info_bar, "version", _("Loading"), NULL);

    gtk_stack_set_visible_child_name (self->stack, "page_spinner");

    exm_detail_view_update (self);

    exm_data_provider_get_async (self->provider, uuid, NULL, on_data_loaded, self);
}

void
exm_detail_view_update (ExmDetailView *self)
{
    if (!self->uuid)
        return;

    // Check if the newly installed extension is the
    // one being displayed in this detail view
    if (exm_manager_is_installed_uuid (self->manager, self->uuid))
        g_object_set (self->ext_install, "state", EXM_INSTALL_BUTTON_STATE_INSTALLED, NULL);
    else
        g_object_set (self->ext_install, "state", EXM_INSTALL_BUTTON_STATE_LOADING, NULL);

}

static void
open_link (ExmDetailView *self,
           const char    *action_name,
           GVariant      *param)
{
    GtkWidget *toplevel;
    GtkUriLauncher *uri = NULL;

    toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));

    if (strcmp (action_name, "detail.open-extensions") == 0)
    {
        uri = gtk_uri_launcher_new (self->uri_extensions);
    }
    else if (strcmp (action_name, "detail.open-homepage") == 0)
    {
        uri = gtk_uri_launcher_new (self->uri_homepage);
    }
    else if (strcmp (action_name, "detail.open-donation") == 0)
    {
        guint val;
        g_variant_get (param, "i", &val);
        uri = gtk_uri_launcher_new (self->uri_donations[val]);
    }
    else
    {
        g_critical ("open_link() invalid action: %s", action_name);
    }

    gtk_uri_launcher_launch (uri, GTK_WINDOW (toplevel), NULL, NULL, NULL);
}

static void
on_bind_manager (ExmDetailView *self)
{
    GListModel *ext_model;
    const gchar *shell_version;

    g_object_get (self->manager,
                  "extensions", &ext_model,
                  "shell-version", &shell_version,
                  NULL);

    g_object_set (self->versions_provider, "shell-version", shell_version, NULL);

    if (self->install_signal_id > 0)
        g_signal_handler_disconnect (self->ext_install, self->install_signal_id);

    self->install_signal_id = g_signal_connect (self->manager,
                                                "install-status",
                                                G_CALLBACK (on_install_status),
                                                self);

    g_signal_connect_swapped (ext_model,
                              "items-changed",
                              G_CALLBACK (exm_detail_view_update),
                              self);
}

static void
breakpoint_apply_cb (ExmDetailView *self)
{
    gtk_widget_remove_css_class (GTK_WIDGET (self->ext_title), "title-1");
    gtk_widget_add_css_class (GTK_WIDGET (self->ext_title), "title-2");
}

static void
breakpoint_unapply_cb (ExmDetailView *self)
{
    gtk_widget_remove_css_class (GTK_WIDGET (self->ext_title), "title-2");
    gtk_widget_add_css_class (GTK_WIDGET (self->ext_title), "title-1");
}

static void
update_headerbar_cb (ExmDetailView *self)
{
    GtkAdjustment *adj;

    adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scroll_area));

    adw_header_bar_set_show_title (ADW_HEADER_BAR (self->header_bar),
                                   gtk_adjustment_get_value (adj) > 0);
}

static void
exm_detail_view_class_init (ExmDetailViewClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->get_property = exm_detail_view_get_property;
    object_class->set_property = exm_detail_view_set_property;

    properties [PROP_MANAGER]
        = g_param_spec_object ("manager",
                               "Manager",
                               "Manager",
                               EXM_TYPE_MANAGER,
                               G_PARAM_READWRITE);

    properties [PROP_SHELL_VERSION]
        = g_param_spec_string ("shell-version",
                               "Shell Version",
                               "Shell Version",
                               NULL,
                               G_PARAM_READWRITE);

    properties [PROP_SCREENSHOT]
        = g_param_spec_object ("screenshot",
                               "Screenshot",
                               "Screenshot",
                               GDK_TYPE_PAINTABLE,
                               G_PARAM_READWRITE);

    g_object_class_install_properties (object_class, N_PROPS, properties);

    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

    gtk_widget_class_set_template_from_resource (widget_class, g_strdup_printf ("%s/exm-detail-view.ui", RESOURCE_PATH));

    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, header_bar);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, title);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, stack);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, error_label);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, error_status);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_icon);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_title);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_author);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_description);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_install);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_screenshot);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_screenshot_container);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_screenshot_popout_button);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, ext_info_bar);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, link_homepage);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, links_donations);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, link_extensions);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, scroll_area);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, comment_box);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, comment_stack);
    gtk_widget_class_bind_template_child (widget_class, ExmDetailView, show_more_btn);

    gtk_widget_class_bind_template_callback (widget_class, breakpoint_apply_cb);
    gtk_widget_class_bind_template_callback (widget_class, breakpoint_unapply_cb);
    gtk_widget_class_bind_template_callback (widget_class, install_remote);

    gtk_widget_class_install_action (widget_class, "detail.open-extensions", NULL, (GtkWidgetActionActivateFunc) open_link);
    gtk_widget_class_install_action (widget_class, "detail.open-homepage", NULL, (GtkWidgetActionActivateFunc) open_link);
    gtk_widget_class_install_action (widget_class, "detail.open-donation", "i", (GtkWidgetActionActivateFunc) open_link);
}

static void
exm_detail_view_init (ExmDetailView *self)
{
    GtkAdjustment *adj;

    g_type_ensure (EXM_TYPE_INSTALL_BUTTON);
    g_type_ensure (EXM_TYPE_SCREENSHOT);
    g_type_ensure (EXM_TYPE_INFO_BAR);

    gtk_widget_init_template (GTK_WIDGET (self));

    self->provider = exm_data_provider_new ();
    self->resolver = exm_image_resolver_new ();
    self->versions_provider = exm_versions_provider_new ();
    self->comment_provider = exm_comment_provider_new ();

    adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scroll_area));

    g_signal_connect_swapped (adj, "value-changed", G_CALLBACK (update_headerbar_cb), self);

    update_headerbar_cb (self);
}
