public class Tuba.Widgets.ScheduledStatus : Gtk.ListBoxRow {
	public signal void deleted (string scheduled_status_id);
	public signal void refresh ();
	public signal void open ();
	public signal void inline_clicked ();

	private bool _draft = false;
	public bool draft {
		get { return _draft; }
		set {
			_draft = value;
			to_draft_button.visible =
			reschedule_button.visible = !value;
			// translators: noun, as in a post that is saved but not posted yet
			schedule_label.label = _("Draft");
			this.activatable = value;
		}
	}

	private bool _is_inline = false;
	public bool is_inline {
		get { return _is_inline; }
		set {
			_is_inline = value;
			separator.visible =
			action_box.visible = !value;
		}
	}

	Gtk.Button reschedule_button;
	Gtk.Button to_draft_button;
	Gtk.Button edit_button;
	Gtk.Box content_box;
	Gtk.Label schedule_label;
	Gtk.Box action_box;
	Gtk.Separator separator;
	construct {
		this.focusable = true;
		this.activatable = false;
		this.css_classes = { "card-spacing", "card", "no-padding" };
		this.overflow = Gtk.Overflow.HIDDEN;

		content_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
		action_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12) {
			margin_top = margin_bottom = margin_start = margin_end = 6
		};
		schedule_label = new Gtk.Label ("") {
			wrap = true,
			wrap_mode = Pango.WrapMode.WORD_CHAR,
			xalign = 0.0f,
			hexpand = true,
			margin_start = 6
		};
		action_box.append (schedule_label);

		var actions_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
		edit_button = new Gtk.Button.from_icon_name ("document-edit-symbolic") {
			css_classes = { "flat" },
			tooltip_text = _("Edit"),
			valign = Gtk.Align.CENTER
		};
		edit_button.clicked.connect (on_edit);
		actions_box.append (edit_button);

		reschedule_button = new Gtk.Button.from_icon_name ("tuba-clock-alt-symbolic") {
			// translators: button, verb, "Reschedule a post"
			tooltip_text = _("Reschedule"),
			css_classes = { "flat" }
		};
		reschedule_button.clicked.connect (on_reschedule);
		actions_box.append (reschedule_button);

		to_draft_button = new Gtk.Button.from_icon_name ("tuba-archive-symbolic") {
			// translators: button, clicking it converts a scheduled post
			//				into a draft post; "Turn into a draft", "Make Draft"
			//				"To Draft" or "Draft" (verb) are alternatives
			tooltip_text = _("Convert into a draft"),
			css_classes = { "flat" }
		};
		to_draft_button.clicked.connect (on_convert_to_draft);
		actions_box.append (to_draft_button);

		Gtk.Button delete_button = new Gtk.Button.from_icon_name ("user-trash-symbolic") {
			css_classes = { "flat", "error" },
			tooltip_text = _("Delete"),
			valign = Gtk.Align.CENTER
		};
		delete_button.clicked.connect (on_delete);
		actions_box.append (delete_button);
		action_box.append (actions_box);

		content_box.append (action_box);
		separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL);
		content_box.append (separator);
		this.child = content_box;

		open.connect (on_activated);
	}

	public ScheduledStatus (API.ScheduledStatus scheduled_status) {
		Object ();
		bind (scheduled_status);
	}

	API.ScheduledStatus bound_scheduled_status;
	Widgets.Status? status_widget = null;
	API.Poll? status_poll = null;
	public void bind (API.ScheduledStatus scheduled_status) {
		if (status_widget != null) content_box.remove (status_widget);
		bound_scheduled_status = scheduled_status;

		GLib.DateTime now_load = new GLib.DateTime.now_local ();
		status_poll = null;
		if (scheduled_status.props.poll != null) {
			status_poll = new API.Poll ("0") {
				multiple = scheduled_status.props.poll.multiple,
				options = new Gee.ArrayList<API.PollOption> ()
			};

			foreach (string poll_option in scheduled_status.props.poll.options) {
				status_poll.options.add (new API.PollOption () {
					title = poll_option,
					votes_count = 0
				});
			}

			status_poll.expires_at = now_load.add_seconds (scheduled_status.props.poll.expires_in).format_iso8601 ();
		}

		var status = new API.Status.empty () {
			account = accounts.active,
			spoiler_text = scheduled_status.props.spoiler_text,
			content = scheduled_status.props.text,
			sensitive = scheduled_status.props.sensitive,
			visibility = scheduled_status.props.visibility,
			media_attachments = scheduled_status.media_attachments,
			tuba_spoiler_revealed = true,
			poll = status_poll,
			created_at = scheduled_status.scheduled_at
		};

		if (scheduled_status.props.language != null) status.language = scheduled_status.props.language;

		var widg = new Widgets.Status (status);
		widg.can_be_opened = false;
		widg.activatable = false;
		widg.toggle_actions_visibility (false);
		widg.menu_button.visible = false;
		widg.date_label.visible = false;
		if (widg.poll != null) {
			widg.poll.usable = false;
			widg.poll.info_label.label = Utils.DateTime.humanize_ago (status_poll.expires_at);
		}

		// Re-parse the date into a MONTH DAY, YEAR (separator) HOUR:MINUTES
		var date_parsed = new GLib.DateTime.from_iso8601 (scheduled_status.scheduled_at, null);

		var delta = date_parsed.to_local ().difference (now_load);
		edit_button.visible = delta >= TimeSpan.HOUR;

		date_parsed = date_parsed.to_timezone (new TimeZone.local ());
		var date_local = _("%B %e, %Y");
		// translators: Scheduled Post title, 'scheduled for <date>'
		schedule_label.label = _("Scheduled for %s").printf (
			date_parsed.format (@"$date_local · %H:%M").replace (" ", "") // %e prefixes with whitespace on single digits
		);

		content_box.append (widg);
		status_widget = widg;
	}

	private class RescheduleDialog : Adw.Dialog {
		public signal void schedule_picked (string iso8601);

		public RescheduleDialog (string iso8601) {
			this.follows_content_size = true;

			var schedule_page = new Dialogs.Schedule (iso8601, _("Reschedule")) {
				can_cancel = true
			};
			schedule_page.schedule_picked.connect (on_schedule_picked);
			schedule_page.cancelled.connect (on_cancel);

			var navigation_view = new Adw.NavigationView ();
			// translators: dialog title when rescheduling a post, reschedule is a verb, post is a noun
			this.title = schedule_page.title = _("Reschedule Post");
			navigation_view.add (schedule_page);

			this.child = navigation_view;
		}

		private void on_cancel () {
			this.force_close ();
		}

		private void on_schedule_picked (string iso8601) {
			schedule_picked (iso8601);
			this.force_close ();
		}
	}

	private void on_convert_to_draft () {
		on_schedule_picked ((new GLib.DateTime.now ()).add_years (3000).format_iso8601 ());
	}

	private void on_reschedule () {
		var dlg = new RescheduleDialog (bound_scheduled_status.scheduled_at);
		dlg.schedule_picked.connect (on_schedule_picked);
		dlg.present (app.main_window);
	}

	private void on_schedule_picked (string iso8601) {
		on_schedule_picked_real.begin (iso8601);
	}

	private async void on_schedule_picked_real (string iso8601) {
		var req = new RequestV2 (@"/api/v1/scheduled_statuses/$(bound_scheduled_status.id)", PUT) {
			account = accounts.active
		};
		req.add_form_data ("scheduled_at", iso8601);

		try {
			var in_stream = yield req.exec (null);
			Json.Parser parser = yield Network.get_parser_from_inputstream_async (in_stream);
			var node = network.parse_node (parser);
			var e = Tuba.Helper.Entity.from_json (node, typeof (API.ScheduledStatus), true);
			if (e is API.ScheduledStatus) {
				if (new GLib.DateTime.from_iso8601 (((API.ScheduledStatus) e).scheduled_at, null).get_year () >= API.ScheduledStatus.DRAFT_YEAR) {
					deleted (bound_scheduled_status.id);
				} else {
					bind ((API.ScheduledStatus) e);
				}
			}
		} catch (Error e) {
			warning (@"Error while rescheduling: $(e.code) $(e.message)");

			// translators:error toast; the variable is an error message
			app.toast (_("Couldn't reschedule: %s").printf (e.message), 0);
		}
	}

	private void on_delete () {
		string title = !_draft
			// translators: as in a post set to be posted sometime in the future
			? _("Delete Scheduled Post?")
			// translators: 'Draft' is not a verb here.
			//				It's equal to 'saved but not posted'
			: _("Delete Draft Post?");

		app.question.begin (
			{title, false},
			null,
			app.main_window,
			{ { _("Delete"), Adw.ResponseAppearance.DESTRUCTIVE }, { _("Cancel"), Adw.ResponseAppearance.DEFAULT } },
			null,
			false,
			(obj, res) => {
				if (app.question.end (res).truthy ()) {
					delete_status.begin ();
				}
			}
		);
	}

	private async void delete_status () {
		var req = new RequestV2 (@"/api/v1/scheduled_statuses/$(bound_scheduled_status.id)", DELETE) { account = accounts.active };

		try {
			yield req.exec (null);
			deleted (bound_scheduled_status.id);
			if (this.is_inline) app.refresh_scheduled_statuses ();
		} catch (Error e) {
			warning (@"Error while deleting scheduled status: $(e.code) $(e.message)");
			app.toast (e.message, 0);
		}
	}

	private void on_activated () {
		if (!_draft || status_widget == null) return;
		if (this.is_inline) {
			inline_clicked ();
			return;
		}

		compose_draft ();
	}

	public void compose_draft () {
		if (!_draft) return;

		new Dialogs.Composer.Dialog.from_scheduled (bound_scheduled_status, true, status_poll, on_draft_posted);
	}

	private void on_draft_posted (API.Status? x) {
		if (_draft) delete_status.begin ();
	}

	private void on_edit () {
		new Dialogs.Composer.Dialog.from_scheduled (bound_scheduled_status, false, status_poll, on_edited);
	}

	private void on_edited (API.Status? x) {
		refresh ();
	}
}
