public class Tuba.Widgets.ActionsRow : Adw.BreakpointBin {
	public signal void reply (Gtk.Button btn);
	public API.Status status { get; set; }

	Widgets.StatusActionButton reply_button;
	Widgets.StatusActionButton reblog_button;
	Widgets.StatusActionButton favorite_button;
	Widgets.StatusActionButton bookmark_button;
	Widgets.StatusActionButton? quote_button = null;

	public ActionsRow (API.Status t_status) {
		Object (status: t_status);

		bind ();
	}

	~ActionsRow () {
		unbind ();
	}

	Binding[] bindings = {};
	ulong[] status_notify_signals = {};
	public void bind () {
		if (bindings.length != 0) return;

		bindings += this.status.bind_property ("replies-count", reply_button, "amount", GLib.BindingFlags.SYNC_CREATE);

		status_notify_signals += this.status.notify["in-reply-to-id"].connect (in_reply_to_id_notify_func);
		in_reply_to_id_notify_func ();

		status_notify_signals += this.status.notify["can-be-boosted"].connect (can_be_boosted_notify_func);
		can_be_boosted_notify_func ();

		bindings += this.status.bind_property ("can-be-boosted", reblog_button, "sensitive", BindingFlags.SYNC_CREATE);
		bindings += this.status.bind_property ("reblogged", reblog_button, "active", GLib.BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
		bindings += this.status.bind_property ("reblogs-count", reblog_button, "amount", GLib.BindingFlags.SYNC_CREATE);

		bindings += this.status.bind_property ("favourited", favorite_button, "active", GLib.BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
		bindings += this.status.bind_property ("favourites-count", favorite_button, "amount", GLib.BindingFlags.SYNC_CREATE);

		bindings += this.status.bind_property ("bookmarked", bookmark_button, "active", GLib.BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);

		if (quote_button != null) {
			bindings += this.status.bind_property ("can-be-quoted", quote_button, "sensitive", BindingFlags.SYNC_CREATE);
			bindings += this.status.bind_property ("quotes-count", quote_button, "amount", BindingFlags.SYNC_CREATE);
			status_notify_signals += this.status.notify["can-be-quoted"].connect (can_be_quoted_notify_fun);
			can_be_quoted_notify_fun ();
		}
	}

	private void in_reply_to_id_notify_func () {
		reply_button.default_icon_name = this.status.in_reply_to_id != null ? "tuba-reply-all-symbolic" : "tuba-reply-sender-symbolic";
	}

	private void can_be_boosted_notify_func () {
		bool src_val = this.status.can_be_boosted;
		reblog_button.sensitive = src_val;

		if (src_val) {
			reblog_button.tooltip_text = _("Boost");
			reblog_button.default_icon_name = "tuba-media-playlist-repeat-symbolic";
		} else {
			// translators: tooltip text when the boost button is disabled
			reblog_button.tooltip_text = _("This post can't be boosted");
			reblog_button.default_icon_name = "tuba-arrows-loop-tall-disabled-symbolic";
		}
	}

	private void can_be_quoted_notify_fun () {
		bool src_val = this.status.can_be_quoted;
		quote_button.sensitive = src_val;

		if (src_val) {
			quote_button.tooltip_text = _("Quote");
			quote_button.default_icon_name = "tuba-quotation-symbolic";
		} else {
			// translators: tooltip text when the quote button is disabled
			quote_button.tooltip_text = _("This post can't be quoted");
			quote_button.default_icon_name = "tuba-quotation-disabled-symbolic";
		}
	}

	public void unbind () {
		foreach (var binding in bindings) {
			binding.unbind ();
		}

		foreach (var signal_id in status_notify_signals) {
			if (GLib.SignalHandler.is_connected (this.status, signal_id))
				this.status.disconnect (signal_id);
		}

		bindings = {};
		status_notify_signals = {};
	}

	Gtk.Box box;
	private bool _is_narrow = false;
	public bool is_narrow {
		get { return _is_narrow; }
		set {
			_is_narrow = value;
			if (value) {
				this.height_request = 34;
				box.spacing = 2;
				this.add_css_class ("narrow");
				reply_button.show_counts = false;
				if (quote_button != null) {
					quote_button.show_counts = false;
					quote_button.smaller = true;
				}
				reply_button.smaller = true;
				reblog_button.smaller = true;
				favorite_button.smaller = true;
			} else {
				this.height_request = 36;
				box.spacing = 6;
				this.remove_css_class ("narrow");
				reply_button.show_counts = settings.show_interaction_counters;
				if (quote_button != null) {
					quote_button.show_counts = settings.show_interaction_counters;
					quote_button.smaller = false;
				}
				reply_button.smaller = false;
				reblog_button.smaller = false;
				favorite_button.smaller = false;
			}
		}
	}

	construct {
		box = new Gtk.Box (HORIZONTAL, 6);
		this.add_css_class ("ttl-post-actions");
		box.spacing = 6;
		this.child = box;
		this.overflow = VISIBLE;
		this.height_request = 36;
		this.width_request = 24;

		reply_button = new Widgets.StatusActionButton.with_icon_name ("tuba-reply-sender-symbolic") {
			show_counts = settings.show_interaction_counters,
			active = false,
			css_classes = { "ttl-status-action-reply", "flat", "circular" },
			halign = Gtk.Align.START,
			hexpand = true,
			// translators: verb
			tooltip_text = _("Reply"),
			//  aria_label_template = (amount) => {
			//  	// translators: Accessibility label on post buttons.
			//  	//				The variable is a number.
			//  	return GLib.ngettext (
			//  		"%s Reply",
			//  		"%s Replies",
			//  		(ulong) amount
			//  	).printf (amount.to_string ());
			//  }
		};
		reply_button.clicked.connect (on_reply_button_clicked);
		box.append (reply_button);

		reblog_button = new Widgets.StatusActionButton.with_icon_name ("tuba-media-playlist-repeat-symbolic") {
			show_counts = settings.show_interaction_counters,
			css_classes = { "ttl-status-action-reblog", "flat", "circular" },
			halign = Gtk.Align.START,
			hexpand = true,
			// translators: verb, boost / reblog / repost a post
			tooltip_text = _("Boost"),
			//  aria_label_template = (amount) => {
			//  	// translators: Accessibility label on post buttons.
			//  	//				The variable is a number.
			//  	return GLib.ngettext (
			//  		"%s Boost",
			//  		"%s Boosts",
			//  		(ulong) amount
			//  	).printf (amount.to_string ());
			//  }
		};
		reblog_button.clicked.connect (on_boost_button_clicked);
		box.append (reblog_button);

		if ((accounts.active.instance_info != null && accounts.active.instance_info.supports_quote_posting) || InstanceAccount.InstanceFeatures.QUOTE in accounts.active.tuba_instance_features) {
			quote_button = new Widgets.StatusActionButton.with_icon_name ("tuba-quotation-symbolic") {
				show_counts = settings.show_interaction_counters,
				css_classes = { "ttl-status-action-quote", "flat", "circular" },
				halign = Gtk.Align.START,
				hexpand = true,
				tooltip_text = _("Quote")
			};
			quote_button.clicked.connect (on_quote_button_clicked);
			box.append (quote_button);
		}

		favorite_button = new Widgets.StatusActionButton.with_icon_name ("tuba-unstarred-symbolic") {
			show_counts = settings.show_interaction_counters,
			active_icon_name = "tuba-starred-symbolic",
			css_classes = { "ttl-status-action-star", "flat", "circular" },
			halign = Gtk.Align.START,
			hexpand = true,
			tooltip_text = _("Favorite"),
			//  aria_label_template = (amount) => {
			//  	// translators: Accessibility label on post buttons.
			//  	//				The variable is a number.
			//  	return GLib.ngettext (
			//  		"%s Favorite",
			//  		"%s Favorites",
			//  		(ulong) amount
			//  	).printf (amount.to_string ());
			//  }
		};
		favorite_button.clicked.connect (on_favorite_button_clicked);
		box.append (favorite_button);

		bookmark_button = new Widgets.StatusActionButton.with_icon_name ("tuba-bookmarks-symbolic") {
			active_icon_name = "tuba-bookmarks-filled-symbolic",
			css_classes = { "ttl-status-action-bookmark", "flat", "circular" },
			halign = Gtk.Align.START,
			hexpand = false,
			// translators: verb, bookmark a post
			tooltip_text = _("Bookmark")
		};
		bookmark_button.clicked.connect (on_bookmark_button_clicked);
		box.append (bookmark_button);


		var condition = new Adw.BreakpointCondition.length (
			Adw.BreakpointConditionLengthType.MAX_WIDTH,
			quote_button == null ? 300 : 380, Adw.LengthUnit.SP
		);
		var breakpoint = new Adw.Breakpoint ((owned) condition);
		breakpoint.add_setter (this, "is-narrow", true);
		add_breakpoint (breakpoint);
	}

	private void on_reply_button_clicked (Gtk.Button btn) {
		if (settings.reply_to_old_post_reminder && Utils.DateTime.is_3_months_old (status.formal.created_at)) {
			app.question.begin (
				// translators: the variable is a datetime with the "old" suffix, e.g. "5 months old", "a day old", "2 years old".
				//				The "old" suffix is translated on the datetime strings, not here
				{_("This post is %s").printf (Utils.DateTime.humanize_old (status.formal.created_at)), false},
				// translators: you can find this string translated on https://github.com/mastodon/mastodon-android/tree/master/mastodon/src/main/res
				//				in the `strings.xml` file inside the `values-` folder that matches your locale under the `old_post_sheet_text` key
				{_("You can still reply, but it may no longer be relevant."), false},
				app.main_window,
				{ { _("Reply"), Adw.ResponseAppearance.SUGGESTED }, { _("Don't remind me again"), Adw.ResponseAppearance.DEFAULT } },
				null,
				false,
				(obj, res) => {
					if (app.question.end (res) == Tuba.Application.QuestionAnswer.NO) {
						settings.reply_to_old_post_reminder = false;
					}
					reply (btn);
				}
			);
		} else {
			reply (btn);
		}
	}

	private void on_bookmark_button_clicked (Gtk.Button btn) {
		var status_btn = btn as Widgets.StatusActionButton;
		if (status_btn.working) return;

		status_btn.block_clicked ();
		status_btn.active = !status_btn.active;

		string action;
		RequestV2 req;
		if (status_btn.active) {
			action = "bookmark";
			req = this.status.bookmark_req ();
		} else {
			action = "unbookmark";
			req = this.status.unbookmark_req ();
		}

		debug (@"Performing status action '$action'…");
		mastodon_action.begin (status_btn, req, action);
	}

	private void on_favorite_button_clicked (Gtk.Button btn) {
		var status_btn = btn as Widgets.StatusActionButton;
		if (status_btn.working) return;

		status_btn.block_clicked ();
		status_btn.active = !status_btn.active;

		string action;
		RequestV2 req;
		if (status_btn.active) {
			action = "favorite";
			req = this.status.favourite_req ();
		} else {
			action = "unfavorite";
			req = this.status.unfavourite_req ();
		}
		status_btn.amount += status_btn.active ? 1 : -1;

		debug (@"Performing status action '$action'…");
		mastodon_action.begin (status_btn, req, action, "favourites-count");
	}

	private bool is_any_missing_alt () {
		if (!status.formal.has_media) return false;
		bool res = false;

		status.formal.media_attachments.foreach (e => {
			if (e.description == null || e.description == "") {
				res = true;
				return false;
			}

			return true;
		});

		return res;
	}

	private void on_boost_button_clicked (Gtk.Button btn) {
		var status_btn = btn as Widgets.StatusActionButton;
		if (status_btn.working) return;

		if (!status_btn.active && settings.boost_alt_text_reminder && is_any_missing_alt ()) {
			var checkbutton = new Gtk.CheckButton () {
				label = _("Don't remind me again"),
				halign = Gtk.Align.CENTER
			};
			app.question.begin (
				// translators: title of dialog shown when trying to boost a post that has attachments without alt text
				{_("This post includes attachments without alt text"), false},
				// translators: subtitle of dialog shown when trying to boost a post that has attachments without alt text;
				//				"it" refers to "this post" (mentioned in the dialog title); "parse" as in read/grasp/understand it
				{_("Some of your followers might not be able to parse it."), false},
				app.main_window,
				{ { _("Boost"), Adw.ResponseAppearance.SUGGESTED }, { _("Cancel"), Adw.ResponseAppearance.DEFAULT } },
				checkbutton,
				false,
				(obj, res) => {
					if (app.question.end (res).truthy ()) {
						boost_button_clicked_real (status_btn);
					}
					if (checkbutton.active) settings.boost_alt_text_reminder = false;
				}
			);
		} else {
			boost_button_clicked_real (status_btn);
		}
	}

	private void boost_button_clicked_real (Widgets.StatusActionButton status_btn) {
		status_btn.block_clicked ();

		if (!status_btn.active && settings.advanced_boost_dialog) {
			Gtk.ListBox visibility_box = new Gtk.ListBox () {
				css_classes = {"content"},
				selection_mode = Gtk.SelectionMode.NONE
			};

			Gtk.CheckButton? group = null; // hashmap is not ordered
			Gee.HashMap<API.Status.Visibility, Gtk.CheckButton> check_buttons = new Gee.HashMap<API.Status.Visibility, Gtk.CheckButton> ();
			for (int i = 0; i < accounts.active.visibility_list.n_items; i++) {
				var visibility = (InstanceAccount.Visibility) accounts.active.visibility_list.get_item (i);
				var reblog_visibility = API.Status.Visibility.from_string (visibility.id);
				if (reblog_visibility == null || reblog_visibility == API.Status.Visibility.DIRECT) continue;

				var checkbutton = new Gtk.CheckButton () {
					css_classes = {"selection-mode"},
					active = settings.default_post_visibility == visibility.id
				};
				check_buttons.set (reblog_visibility, checkbutton);

				if (group != null) {
					checkbutton.group = group;
				} else {
					group = checkbutton;
				}

				var visibility_row = new Adw.ActionRow () {
					title = visibility.name,
					subtitle = visibility.description,
					activatable_widget = checkbutton
				};
				visibility_row.add_prefix (new Gtk.Image.from_icon_name (visibility.icon_name));
				visibility_row.add_prefix (checkbutton);

				visibility_box.append (visibility_row);
			}

			var dlg = new Adw.AlertDialog (
				// translators: dialog title to select boost visibility (public, unlisted, private)
				_("Boost with Visibility"),
				null
			) {
				extra_child = visibility_box
			};
			dlg.add_response ("no", _("Cancel"));
			// don't add the "quote" option if there's a quote button available
			if (quote_button == null) {
				dlg.add_response ("quote", _("Quote"));
			}
			dlg.add_response ("yes", _("Boost"));
			dlg.set_response_appearance ("yes", Adw.ResponseAppearance.SUGGESTED);

			dlg.response.connect (res => {
				dlg.destroy ();

				switch (res) {
					case "yes":
					case "quote":
						API.Status.Visibility? reblog_visibility = null;
						check_buttons.foreach (e => {
							if (((Gtk.CheckButton) e.value).active) {
								reblog_visibility = (API.Status.Visibility) e.key;
								return false;
							}

							return true;
						});

						switch (res) {
							case "yes":
								commit_boost (status_btn, reblog_visibility);
								break;
							case "quote":
								bool supports_quotes =
									status.formal.can_be_quoted
									&& (
										(accounts.active.instance_info != null && accounts.active.instance_info.supports_quote_posting)
										|| InstanceAccount.InstanceFeatures.QUOTE in accounts.active.tuba_instance_features
									);
								new Dialogs.Composer.Dialog.quote (status.formal, reblog_visibility, supports_quotes);
								status_btn.unblock_clicked ();
								break;
							default:
								assert_not_reached ();
						}
						break;
					default:
						status_btn.unblock_clicked ();
						break;
				}

				group = null;
				check_buttons.clear ();
			});

			dlg.present (app.main_window);
		} else {
			commit_boost (status_btn);
		}
	}

	private void on_quote_button_clicked () {
		new Dialogs.Composer.Dialog.quote (status.formal, null, true);
	}

	private void commit_boost (Widgets.StatusActionButton status_btn, API.Status.Visibility? visibility = null) {
			status_btn.active = !status_btn.active;

			string action;
			RequestV2 req;
			if (status_btn.active) {
				action = "reblog";
				req = this.status.reblog_req (visibility);
			} else {
				action = "unreblog";
				req = this.status.unreblog_req ();
			}

			status_btn.amount += status_btn.active ? 1 : -1;
			debug (@"Performing status action '$action'…");
			mastodon_action.begin (status_btn, req, action, "reblogs-count");
	}

	private async void mastodon_action (Widgets.StatusActionButton status_btn, RequestV2 req, string action, string? count_property = null) {
		try {
			yield req.exec (null);
			if (count_property != null) {
				int64 status_property_count;
				this.status.get (count_property, out status_property_count);
				this.status.set (count_property, status_property_count + (status_btn.active ? 1 : -1));
			}

			// Not reliable, it sometimes returns wrong info.
			// But it should be the desired one, as it updated the whole object.
			//
			//  var msg = req.await.end (res);

			//  var parser = Network.get_parser_from_inputstream (msg.response_body);
			//  var node = network.parse_node (parser);
			//  var e = Tuba.Helper.Entity.from_json (node, typeof (API.Status), true);

			//  if (count_property != null) {
			//  	int64 e_property_count;
			//  	int64 status_property_count;
			//  	((API.Status) e).get (count_property, out e_property_count);
			//  	this.status.get (count_property, out status_property_count);
			//  	if (e_property_count == status_property_count) {
			//  		((API.Status) e).set (count_property, e_property_count + (status_btn.active ? 1 : -1));
			//  	}
			//  }

			//  this.status.patch (e);
			debug (@"Status action '$action' complete");
		} catch (Error e) {
			warning (@"Couldn't perform action \"$action\" on a Status:");
			warning (e.message);
			// translators: error title
			app.toast ("%s: %s".printf (_("Network Error"), e.message));

			if (count_property != null)
				status_btn.amount += status_btn.active ? -1 : 1;
			status_btn.active = !status_btn.active;
		}

		status_btn.unblock_clicked ();
	}
}
