using Gtk;

[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/dialogs/list_editor.ui")]
public class Tootle.Dialogs.ListEditor: Hdy.Window {

	[GtkTemplate (ui = "/com/github/bleakgrey/tootle/ui/widgets/list_editor_item.ui")]
	class Item : ListBoxRow {

		public ListEditor editor { get; construct set; }
		public API.Account acc { get; construct set; }
		public bool committed { get; construct set; }

		[GtkChild]
		Widgets.RichLabel label;
		[GtkChild]
		Widgets.RichLabel handle;
		[GtkChild]
		ToggleButton status;

		public Item (ListEditor editor, API.Account acc, bool committed) {
			this.editor = editor;
			this.acc = acc;
			this.committed = committed;
			acc.bind_property ("display-name", label, "text", BindingFlags.SYNC_CREATE);
			acc.bind_property ("handle", handle, "text", BindingFlags.SYNC_CREATE);
			status.active = committed;
			status.sensitive = true;
		}

		[GtkCallback]
		void on_toggled () {
			if (!status.sensitive)
				return;

			if (status.active) {
				debug (@"To add: $(acc.id)");
				editor.to_add.add (acc.id);
				editor.to_remove.remove (acc.id);
			}
			else {
				debug (@"To remove: $(acc.id)");
				editor.to_add.remove (acc.id);
				editor.to_remove.add (acc.id);
			}
			committed = status.active;
			if (!editor.working)
				editor.dirty = true;
		}

	}

	public API.List list { get; set; }
	public bool working { get; set; default = false; }
	public bool exists { get; set; default = false; }
	public bool dirty { get; set; default = false; }

	Soup.Message? search_req = null;

	public Gee.ArrayList<string> to_add = new Gee.ArrayList<string> ();
	public Gee.ArrayList<string> to_remove = new Gee.ArrayList<string> ();

	[GtkChild]
	Button save_btn;
	[GtkChild]
	Stack save_btn_stack;
	[GtkChild]
	Entry name_entry;
	[GtkChild]
	SearchEntry search_entry;
	[GtkChild]
	ListBox listbox;

	[GtkChild]
	InfoBar infobar;
	[GtkChild]
	Label infobar_label;

	public signal void done ();

	construct {
		transient_for = window;
		show ();
	}

	public ListEditor.empty () {
		var obj = new API.List () {
			title = _("Untitled")
		};
		Object (list: obj);
		init ();
	}

	public ListEditor (API.List list) {
		Object (list: list, working: true, exists: true);
		init ();

		new Request.GET (@"/api/v1/lists/$(list.id)/accounts")
			.with_account (accounts.active)
			.with_ctx (this)
			.on_error (on_error)
			.then ((sess, msg) => {
				Network.parse_array (msg, node => {
					var acc = API.Account.from (node);
					add_account (acc, true);
				});
				working = false;
			})
			.exec ();
	}

	void init () {
		notify["working"].connect (on_state_changed);
		list.bind_property ("title", name_entry, "text", BindingFlags.SYNC_CREATE);

		ulong dirty_sigid = 0;
		dirty_sigid = name_entry.changed.connect (() => {
			dirty = true;
			name_entry.disconnect (dirty_sigid);
		});

		on_state_changed (null);
	}

	void on_state_changed (ParamSpec? p) {
		save_btn_stack.visible_child_name = working ? "working" : "done";
		save_btn.sensitive = search_entry.sensitive = name_entry.sensitive = !working;
	}

	void on_error (int32 code, string msg) {
		warning (msg);
		infobar_label.label = msg;
		infobar.revealed = true;
	}

	[GtkCallback]
	void infobar_response (int i) {
		infobar.revealed = false;
	}

	void request_search (string q) {
		debug (@"Searching for: \"$q\"...");
		if (search_req != null) {
			network.cancel (search_req);
			search_req = null;
		}

		search_req = new Request.GET ("/api/v1/accounts/search")
			.with_account (accounts.active)
			.with_ctx (this)
			.with_param ("resolve", "false")
			.with_param ("limit", "8")
			.with_param ("following", "true")
			.with_param ("q", q)
			.then ((sess, msg) => {
				Network.parse_array (msg, node => {
					var acc = API.Account.from (node);
					add_account (acc, false, 0);
				});
			})
			.on_error (on_error)
			.exec ();
	}

	void add_account (API.Account acc, bool added, int order = -1) {
		var exists = false;
		listbox.@foreach (w => {
			var i = w as Item;
			if (i != null) {
				if (i.acc.id == acc.id)
					exists = true;
			}
		});

		if (!exists) {
			var item = new Item (this, acc, added);
			listbox.insert (item, order);
		}
	}

	void invalidate () {
		listbox.@foreach (w => {
			var i = w as Item;
			if (i != null) {
				if (!i.committed)
					i.destroy ();
			}
		});
	}


	[GtkCallback]
	void validate () {
		var has_title = name_entry.text.replace (" ", "") != "";
		save_btn.sensitive = has_title;
	}

	[GtkCallback]
	void on_cancel_clicked () {
		if (dirty) {
			var yes = app.question (
				_("Discard changes?"),
				_("You need to save the list if you want to keep them."),
				this
			);
			if (yes)
				destroy ();
		}
		else
			destroy ();
	}

	[GtkCallback]
	void on_search_changed () {
		var q = search_entry.text.chug ().chomp ();

		if (q.char_count () < 3)
			invalidate ();
		else if (q != "") {
			invalidate ();
			request_search (q);
		}
	}

	[GtkCallback]
	void on_save_clicked () {
		working = true;
		transaction.begin ((obj, res) => {
			try {
				transaction.end (res);

				done ();
				destroy ();
			}
			catch (Error e) {
				working = false;
				on_error (0, e.message);
			}
		});
	}

	async void transaction () throws Error {
		if (!exists) {
			message ("Creating list...");
			var req = new Request.POST ("/api/v1/lists")
				.with_account (accounts.active)
				.with_param ("title", name_entry.text);
			yield req.await ();

			message ("Received new List entity");
			var node = network.parse_node (req);
			list = API.List.from (node);
		}
		else {
			message ("Updating list title...");
			yield new Request.PUT (@"/api/v1/lists/$(list.id)")
				.with_account (accounts.active)
				.with_param ("title", name_entry.text)
				.await ();
		}

		if (!to_add.is_empty) {
			message ("Adding accounts to list...");
			var id_array = Request.array2string (to_add, "account_ids");
		 	yield new Request.POST (@"/api/v1/lists/$(list.id)/accounts/?$id_array")
		 		.with_account (accounts.active)
		 		.await ();
		}

		if (!to_remove.is_empty) {
			message ("Removing accounts from list...");
			var id_array = Request.array2string (to_remove, "account_ids");
		 	yield new Request.DELETE (@"/api/v1/lists/$(list.id)/accounts/?$id_array")
		 		.with_account (accounts.active)
		 		.await ();
		}

		message ("OK: List updated");
		list.title = name_entry.text;
	}

}
