/* SPDX-FileCopyrightText: 2025 - Sébastien Wilmet
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "gtex-find-bar.h"
#include <glib/gi18n.h>
#include <tepl/tepl.h>

/**
 * SECTION:find-bar
 * @title: GtexFindBar
 * @short_description: Find bar widget
 *
 * #GtexFindBar contains only the widget part of the Find and Replace feature,
 * and is an horizontal bar.
 */

struct _GtexFindBarPrivate
{
	GtexFindBarMode mode;
	GtkButton *arrow_button;

	/* Find component */
	GtkSearchEntry *find_entry;
	GtkButton *previous_button;
	GtkButton *next_button;
	GtkButton *close_button;
	GtkLabel *info_label;
	GtkMenu *options_menu;
	GtkCheckMenuItem *check_menu_item_for_case_sensitive;
	GtkCheckMenuItem *check_menu_item_for_entire_words;

	/* Replace component */
	GtkGrid *replace_grid;
	GtkSearchEntry *replace_entry;
	GtkButton *replace_button;
	GtkButton *replace_all_button;
};

G_DEFINE_TYPE_WITH_PRIVATE (GtexFindBar, gtex_find_bar, GTK_TYPE_BIN)

static const gchar *
get_arrow_icon_name_for_mode (GtexFindBarMode mode)
{
	switch (mode)
	{
		case GTEX_FIND_BAR_MODE_FIND_ONLY:
			return "go-down-symbolic";

		case GTEX_FIND_BAR_MODE_FIND_AND_REPLACE:
			return "go-up-symbolic";

		default:
			break;
	}

	g_return_val_if_reached (NULL);
}

static GtkButton *
create_button (const gchar *icon_name)
{
	GtkButton *button;

	button = GTK_BUTTON (gtk_button_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON));
	gtk_button_set_relief (button, GTK_RELIEF_NONE);

	return button;
}

static GtkGrid *
create_main_grid (void)
{
	GtkGrid *main_grid;

	main_grid = GTK_GRID (gtk_grid_new ());
	gtk_grid_set_column_spacing (main_grid, 3);
	gtk_grid_set_row_spacing (main_grid, 3);
	gtk_widget_set_margin_start (GTK_WIDGET (main_grid), 3);

	return main_grid;
}

static GtkButton *
create_arrow_button (void)
{
	const gchar *icon_name;

	icon_name = get_arrow_icon_name_for_mode (GTEX_FIND_BAR_MODE_FIND_ONLY);

	return GTK_BUTTON (gtk_button_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON));
}

static GtkSearchEntry *
create_find_entry (void)
{
	GtkSearchEntry *find_entry;

	find_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
	gtk_widget_set_tooltip_text (GTK_WIDGET (find_entry), _("Search for"));
	gtk_entry_set_width_chars (GTK_ENTRY (find_entry), 25);
	gtk_entry_set_icon_from_icon_name (GTK_ENTRY (find_entry),
					   GTK_ENTRY_ICON_PRIMARY,
					   "document-properties-symbolic");
	gtk_entry_set_icon_activatable (GTK_ENTRY (find_entry), GTK_ENTRY_ICON_PRIMARY, TRUE);
	gtk_entry_set_icon_sensitive (GTK_ENTRY (find_entry), GTK_ENTRY_ICON_PRIMARY, TRUE);

	return find_entry;
}

static GtkButton *
create_previous_button (void)
{
	GtkButton *previous_button;

	previous_button = create_button ("go-up");
	gtk_widget_set_tooltip_text (GTK_WIDGET (previous_button),
				     _("Go to the previous match"));

	return previous_button;
}

static GtkButton *
create_next_button (void)
{
	GtkButton *next_button;

	next_button = create_button ("go-down");
	gtk_widget_set_tooltip_text (GTK_WIDGET (next_button),
				     _("Go to the next match"));

	return next_button;
}

static GtkLabel *
create_info_label (void)
{
	GtkLabel *info_label;

	info_label = GTK_LABEL (gtk_label_new (NULL));
	gtk_widget_set_margin_start (GTK_WIDGET (info_label), 12);

	return info_label;
}

static GtkGrid *
create_replace_grid (void)
{
	GtkGrid *replace_grid;

	replace_grid = GTK_GRID (gtk_grid_new ());
	gtk_grid_set_column_spacing (replace_grid, 2);

	return replace_grid;
}

static GtkSearchEntry *
create_replace_entry (void)
{
	GtkSearchEntry *replace_entry;

	replace_entry = GTK_SEARCH_ENTRY (gtk_search_entry_new ());
	gtk_widget_set_tooltip_text (GTK_WIDGET (replace_entry), _("Replace with"));
	gtk_entry_set_width_chars (GTK_ENTRY (replace_entry), 25);
	gtk_entry_set_icon_from_icon_name (GTK_ENTRY (replace_entry),
					   GTK_ENTRY_ICON_PRIMARY,
					   NULL);

	return replace_entry;
}

static GtkButton *
create_replace_button (void)
{
	GtkButton *replace_button;

	replace_button = create_button ("edit-find-replace");
	gtk_widget_set_tooltip_text (GTK_WIDGET (replace_button), _("Replace"));

	return replace_button;
}

static GtkButton *
create_replace_all_button (void)
{
	GtkButton *replace_all_button;
	GtkWidget *image;
	GtkWidget *label;
	GtkGrid *hgrid;

	replace_all_button = GTK_BUTTON (gtk_button_new ());
	gtk_button_set_relief (replace_all_button, GTK_RELIEF_NONE);
	gtk_widget_set_tooltip_text (GTK_WIDGET (replace_all_button),
				     _("Replace All"));

	image = gtk_image_new_from_icon_name ("edit-find-replace", GTK_ICON_SIZE_BUTTON);
	label = gtk_label_new (_("All"));

	hgrid = GTK_GRID (gtk_grid_new ());
	gtk_grid_set_column_spacing (hgrid, 8);
	gtk_widget_set_valign (GTK_WIDGET (hgrid), GTK_ALIGN_CENTER);

	gtk_container_add (GTK_CONTAINER (hgrid), image);
	gtk_container_add (GTK_CONTAINER (hgrid), label);
	gtk_container_add (GTK_CONTAINER (replace_all_button), GTK_WIDGET (hgrid));

	return replace_all_button;
}

static void
init_options_menu (GtexFindBar *find_bar)
{
	g_assert (find_bar->priv->options_menu == NULL);
	g_assert (find_bar->priv->check_menu_item_for_case_sensitive == NULL);
	g_assert (find_bar->priv->check_menu_item_for_entire_words == NULL);

	find_bar->priv->options_menu = GTK_MENU (gtk_menu_new ());
	g_object_ref_sink (find_bar->priv->options_menu);

	find_bar->priv->check_menu_item_for_case_sensitive =
		GTK_CHECK_MENU_ITEM (gtk_check_menu_item_new_with_label (_("Case sensitive")));
	find_bar->priv->check_menu_item_for_entire_words =
		GTK_CHECK_MENU_ITEM (gtk_check_menu_item_new_with_label (_("Entire words only")));

	gtk_menu_shell_append (GTK_MENU_SHELL (find_bar->priv->options_menu),
			       GTK_WIDGET (find_bar->priv->check_menu_item_for_case_sensitive));
	gtk_menu_shell_append (GTK_MENU_SHELL (find_bar->priv->options_menu),
			       GTK_WIDGET (find_bar->priv->check_menu_item_for_entire_words));
	gtk_widget_show_all (GTK_WIDGET (find_bar->priv->options_menu));
}

static void
update_arrow_image (GtexFindBar *find_bar)
{
	GtkImage *arrow_image;
	const gchar *arrow_icon_name;

	arrow_image = GTK_IMAGE (gtk_button_get_image (find_bar->priv->arrow_button));
	arrow_icon_name = get_arrow_icon_name_for_mode (find_bar->priv->mode);
	gtk_image_set_from_icon_name (arrow_image, arrow_icon_name, GTK_ICON_SIZE_BUTTON);
}

static void
update_arrow_button_tooltip (GtexFindBar *find_bar)
{
	const gchar *tooltip_text = NULL;

	switch (find_bar->priv->mode)
	{
		case GTEX_FIND_BAR_MODE_FIND_ONLY:
			tooltip_text = _("Show the “Replace” component");
			break;

		case GTEX_FIND_BAR_MODE_FIND_AND_REPLACE:
			tooltip_text = _("Hide the “Replace” component");
			break;

		default:
			g_warn_if_reached ();
			break;
	}

	gtk_widget_set_tooltip_text (GTK_WIDGET (find_bar->priv->arrow_button), tooltip_text);
}

static void
update_close_button_tooltip (GtexFindBar *find_bar)
{
	const gchar *tooltip_text = NULL;

	switch (find_bar->priv->mode)
	{
		case GTEX_FIND_BAR_MODE_FIND_ONLY:
			tooltip_text = _("Close the “Find” bar");
			break;

		case GTEX_FIND_BAR_MODE_FIND_AND_REPLACE:
			tooltip_text = _("Close the “Find and Replace” bar");
			break;

		default:
			g_warn_if_reached ();
			break;
	}

	gtk_widget_set_tooltip_text (GTK_WIDGET (find_bar->priv->close_button), tooltip_text);
}

static void
update_replace_grid_visibility (GtexFindBar *find_bar)
{
	gtk_widget_set_visible (GTK_WIDGET (find_bar->priv->replace_grid),
				find_bar->priv->mode == GTEX_FIND_BAR_MODE_FIND_AND_REPLACE);
}

static void
arrow_button_clicked_cb (GtkButton *arrow_button,
			 gpointer   user_data)
{
	GtexFindBar *find_bar = GTEX_FIND_BAR (user_data);

	if (find_bar->priv->mode == GTEX_FIND_BAR_MODE_FIND_ONLY)
	{
		gtex_find_bar_set_mode (find_bar, GTEX_FIND_BAR_MODE_FIND_AND_REPLACE);
	}
	else
	{
		gtex_find_bar_set_mode (find_bar, GTEX_FIND_BAR_MODE_FIND_ONLY);
	}
}

static void
find_entry_icon_press_cb (GtkSearchEntry       *find_entry,
			  GtkEntryIconPosition  icon_pos,
			  GdkEvent             *event,
			  gpointer              user_data)
{
	GtexFindBar *find_bar = GTEX_FIND_BAR (user_data);

	if (icon_pos == GTK_ENTRY_ICON_PRIMARY)
	{
		gtk_menu_popup_at_widget (find_bar->priv->options_menu,
					  GTK_WIDGET (find_entry),
					  GDK_GRAVITY_SOUTH_WEST,
					  GDK_GRAVITY_NORTH_WEST,
					  event);
	}
}

static void
gtex_find_bar_dispose (GObject *object)
{
	GtexFindBar *find_bar = GTEX_FIND_BAR (object);

	find_bar->priv->arrow_button = NULL;

	find_bar->priv->find_entry = NULL;
	find_bar->priv->previous_button = NULL;
	find_bar->priv->next_button = NULL;
	find_bar->priv->close_button = NULL;
	find_bar->priv->info_label = NULL;

	g_clear_object (&find_bar->priv->options_menu);
	find_bar->priv->check_menu_item_for_case_sensitive = NULL;
	find_bar->priv->check_menu_item_for_entire_words = NULL;

	find_bar->priv->replace_grid = NULL;
	find_bar->priv->replace_entry = NULL;
	find_bar->priv->replace_button = NULL;
	find_bar->priv->replace_all_button = NULL;

	G_OBJECT_CLASS (gtex_find_bar_parent_class)->dispose (object);
}

static void
gtex_find_bar_class_init (GtexFindBarClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->dispose = gtex_find_bar_dispose;
}

static void
gtex_find_bar_init (GtexFindBar *find_bar)
{
	GtkGrid *main_grid;
	GtkGrid *find_hgrid;

	find_bar->priv = gtex_find_bar_get_instance_private (find_bar);

	/* General */

	main_grid = create_main_grid ();
	gtk_container_add (GTK_CONTAINER (find_bar), GTK_WIDGET (main_grid));

	find_bar->priv->arrow_button = create_arrow_button ();
	gtk_grid_attach (main_grid, GTK_WIDGET (find_bar->priv->arrow_button), 0, 0, 1, 1);

	/* Find component */

	find_hgrid = GTK_GRID (gtk_grid_new ());
	gtk_grid_set_column_spacing (find_hgrid, 2);

	find_bar->priv->find_entry = create_find_entry ();
	find_bar->priv->previous_button = create_previous_button ();
	find_bar->priv->next_button = create_next_button ();
	find_bar->priv->close_button = GTK_BUTTON (tepl_utils_create_close_button ());
	find_bar->priv->info_label = create_info_label ();

	gtk_container_add (GTK_CONTAINER (find_hgrid),
			   GTK_WIDGET (find_bar->priv->find_entry));
	gtk_container_add (GTK_CONTAINER (find_hgrid),
			   GTK_WIDGET (find_bar->priv->previous_button));
	gtk_container_add (GTK_CONTAINER (find_hgrid),
			   GTK_WIDGET (find_bar->priv->next_button));
	gtk_container_add (GTK_CONTAINER (find_hgrid),
			   GTK_WIDGET (find_bar->priv->close_button));
	gtk_container_add (GTK_CONTAINER (find_hgrid),
			   GTK_WIDGET (find_bar->priv->info_label));
	gtk_grid_attach (main_grid, GTK_WIDGET (find_hgrid), 1, 0, 1, 1);

	init_options_menu (find_bar);

	/* Replace component */

	find_bar->priv->replace_grid = create_replace_grid ();
	find_bar->priv->replace_entry = create_replace_entry ();
	find_bar->priv->replace_button = create_replace_button ();
	find_bar->priv->replace_all_button = create_replace_all_button ();

	gtk_container_add (GTK_CONTAINER (find_bar->priv->replace_grid),
			   GTK_WIDGET (find_bar->priv->replace_entry));
	gtk_container_add (GTK_CONTAINER (find_bar->priv->replace_grid),
			   GTK_WIDGET (find_bar->priv->replace_button));
	gtk_container_add (GTK_CONTAINER (find_bar->priv->replace_grid),
			   GTK_WIDGET (find_bar->priv->replace_all_button));
	gtk_grid_attach (main_grid, GTK_WIDGET (find_bar->priv->replace_grid), 1, 1, 1, 1);

	/* Initial setup */

	gtex_find_bar_set_mode (find_bar, GTEX_FIND_BAR_MODE_FIND_ONLY);

	/* Signal connections */

	g_signal_connect_object (find_bar->priv->arrow_button,
				 "clicked",
				 G_CALLBACK (arrow_button_clicked_cb),
				 find_bar,
				 G_CONNECT_DEFAULT);

	g_signal_connect_object (find_bar->priv->find_entry,
				 "icon-press",
				 G_CALLBACK (find_entry_icon_press_cb),
				 find_bar,
				 G_CONNECT_DEFAULT);
}

/**
 * gtex_find_bar_new:
 *
 * Returns: (transfer floating): a new #GtexFindBar widget.
 */
GtexFindBar *
gtex_find_bar_new (void)
{
	return g_object_new (GTEX_TYPE_FIND_BAR, NULL);
}

/**
 * gtex_find_bar_set_mode:
 * @find_bar: a #GtexFindBar.
 * @new_mode: the new mode to set.
 */
void
gtex_find_bar_set_mode (GtexFindBar     *find_bar,
			GtexFindBarMode  new_mode)
{
	g_return_if_fail (GTEX_IS_FIND_BAR (find_bar));

	find_bar->priv->mode = new_mode;

	update_arrow_image (find_bar);
	update_arrow_button_tooltip (find_bar);
	update_close_button_tooltip (find_bar);
	update_replace_grid_visibility (find_bar);
}

/**
 * gtex_find_bar_get_find_entry:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkSearchEntry of @find_bar to search for
 *   text.
 */
GtkSearchEntry *
gtex_find_bar_get_find_entry (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->find_entry;
}

/**
 * gtex_find_bar_get_previous_button:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkButton of @find_bar to go to the previous
 *   match.
 */
GtkButton *
gtex_find_bar_get_previous_button (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->previous_button;
}

/**
 * gtex_find_bar_get_next_button:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkButton of @find_bar to go to the next
 *   match.
 */
GtkButton *
gtex_find_bar_get_next_button (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->next_button;
}

/**
 * gtex_find_bar_get_close_button:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkButton to close @find_bar.
 */
GtkButton *
gtex_find_bar_get_close_button (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->close_button;
}

/**
 * gtex_find_bar_get_info_label:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkLabel of @find_bar to show information
 *   about the number of matches.
 */
GtkLabel *
gtex_find_bar_get_info_label (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->info_label;
}

/**
 * gtex_find_bar_get_check_menu_item:
 * @find_bar: a #GtexFindBar.
 * @item_name: the item name.
 *
 * Returns: (transfer none): the #GtkCheckMenuItem of @find_bar corresponding to
 *   @item_name.
 */
GtkCheckMenuItem *
gtex_find_bar_get_check_menu_item (GtexFindBar *find_bar,
				   const gchar *item_name)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	g_return_val_if_fail (item_name != NULL, NULL);

	if (g_str_equal (item_name, "case-sensitive"))
	{
		return find_bar->priv->check_menu_item_for_case_sensitive;
	}
	else if (g_str_equal (item_name, "entire-words"))
	{
		return find_bar->priv->check_menu_item_for_entire_words;
	}

	g_return_val_if_reached (NULL);
}

/**
 * gtex_find_bar_get_replace_grid:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkGrid of @find_bar containing the “Replace”
 *   component.
 */
GtkGrid *
gtex_find_bar_get_replace_grid (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->replace_grid;
}

/**
 * gtex_find_bar_get_replace_entry:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkSearchEntry of @find_bar for the
 *   replacement text.
 */
GtkSearchEntry *
gtex_find_bar_get_replace_entry (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->replace_entry;
}

/**
 * gtex_find_bar_get_replace_button:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkButton of @find_bar to replace one match.
 */
GtkButton *
gtex_find_bar_get_replace_button (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->replace_button;
}

/**
 * gtex_find_bar_get_replace_all_button:
 * @find_bar: a #GtexFindBar.
 *
 * Returns: (transfer none): the #GtkButton of @find_bar to replace all matches.
 */
GtkButton *
gtex_find_bar_get_replace_all_button (GtexFindBar *find_bar)
{
	g_return_val_if_fail (GTEX_IS_FIND_BAR (find_bar), NULL);
	return find_bar->priv->replace_all_button;
}
