public class Tuba.API.Status : Entity, Widgetizable, SearchResult {

	~Status () {
		debug (@"[OBJ] Destroyed $(uri ?? "")");
	}

	public class QuoteApproval : Entity {
		public Gee.ArrayList<string>? automatic { get; set; default = null; }
		public Gee.ArrayList<string>? manual { get; set; default = null; }
		public string current_user { get; set; }

		public QuotePolicy to_quote_policy () {
			if (
				(this.current_user == "automatic" || this.current_user == "manual")
				&& (
					(this.automatic != null && this.automatic.size > 0)
					|| (this.manual != null && this.manual.size > 0)
				)
			) {
				foreach (var qp in (
					this.automatic != null && this.automatic.size > 0
					? this.automatic : this.manual
				)) {
					switch (qp) {
						case "public":
							return QuotePolicy.PUBLIC;
						case "followers":
						case "following":
							return QuotePolicy.FOLLOWERS;
					}
				}
			}

			return QuotePolicy.NOBODY;
		}

		public override Type deserialize_array_type (string prop) {
			switch (prop) {
				case "automatic":
				case "manual":
					return Type.STRING;
			}

			return base.deserialize_array_type (prop);
		}
	}

	public string id { get; set; }
	public API.Account account { get; set; }
	public string uri { get; set; }
	public string? spoiler_text { get; set; default = null; }
	public string? in_reply_to_id { get; set; default = null; }
	public string? in_reply_to_account_id { get; set; default = null; }
	public string content { get; set; default = ""; }
	public StatusApplication? application { get; set; default = null; }
	public int64 replies_count { get; set; default = 0; }
	public int64 quotes_count { get; set; default = 0; }
	public int64 reblogs_count { get; set; default = 0; }
	public int64 favourites_count { get; set; default = 0; }
	public string created_at { get; set; default = "0"; }
	public bool reblogged { get; set; default = false; }
	public bool favourited { get; set; default = false; }
	public bool bookmarked { get; set; default = false; }
	public bool local_only { get; set; default = false; }
	public bool sensitive { get; set; default = false; }
	public bool muted { get; set; default = false; }
	public bool pinned { get; set; default = false; }
	public string? edited_at { get; set; default = null; }
	public string visibility { get; set; default = settings.default_post_visibility; }
	public API.Status? reblog { get; set; default = null; }
	public API.Quote? quote { get; set; default = null; }
	//  public API.Akkoma? akkoma { get; set; default = null; }
	public Gee.ArrayList<API.Mention>? mentions { get; set; default = null; }
	public Gee.ArrayList<API.EmojiReaction>? reactions { get; set; default = null; }
	public Gee.ArrayList<API.EmojiReaction>? emoji_reactions { get; set; default = null; }
	public API.Pleroma.Status? pleroma { get; set; default = null; }
	public Gee.ArrayList<API.Attachment>? media_attachments { get; set; default = null; }
	public API.Poll? poll { get; set; default = null; }
	public Gee.ArrayList<API.Emoji>? emojis { get; set; }
	public API.PreviewCard? card { get; set; default = null; }
	public Gee.ArrayList<API.Filters.FilterResult>? filtered { get; set; default = null; }
	public QuoteApproval? quote_approval { get; set; default = null; }
	public bool tuba_had_quote { get; set; default = false; }
	public Gee.ArrayList<API.Collection>? tagged_collections { get; set; default = null; }

	public override Type deserialize_array_type (string prop) {
		switch (prop) {
			case "reactions":
			case "emoji-reactions":
				return typeof (API.EmojiReaction);
			case "mentions":
				return typeof (API.Mention);
			case "media-attachments":
				return typeof (API.Attachment);
			case "emojis":
				return typeof (API.Emoji);
			case "filtered":
				return typeof (API.Filters.FilterResult);
			case "tagged-collections":
				return typeof (API.Collection);
		}

		return base.deserialize_array_type (prop);
	}

	public bool tuba_filter_hidden {
		get {
			if (
				filtered == null
				|| filtered.size == 0
				|| this.formal.account.is_self ()
			) return false;

			bool res = false;
			filtered.@foreach (e => {
				if (API.Filters.Filter.FilterAction.from_string (e.filter.filter_action) == HIDE) {
					res = true;
					return false;
				}
				return true;
			});

			return res;
		}
	}

	public string? tuba_filter_warn {
		owned get {
			if (filtered == null || filtered.size == 0) return null;

			string? res = null;
			filtered.@foreach (e => {
				if (API.Filters.Filter.FilterAction.from_string (e.filter.filter_action) != HIDE) {
					res = e.filter.title;
					return false;
				}
				return true;
			});

			return res;
		}
	}

	public GLib.Regex? tuba_search_query_regex { get; set; default = null; }
	public Tuba.Views.Thread.ThreadRole tuba_thread_role { get; set; default = Tuba.Views.Thread.ThreadRole.NONE; }
	public bool tuba_spoiler_revealed { get; set; default = settings.show_spoilers; }
	public bool tuba_translatable { get; set; default = false; }

	//  public string clean_content {
	//      get {
	//          if (quote != null && akkoma != null && akkoma.source != null && akkoma.source.content != null) {
	//              return akkoma.source.content;
	//          }

	//          return content;
	//      }
	//  }

	private string _language = settings.default_language;
	public string language {
		get {
			return _language;
		}
		set {
			if (value != null) tuba_translatable = true;
			_language = value ?? settings.default_language;
		}
	}

	public Gee.HashMap<string, string>? emojis_map {
		owned get {
			return gen_emojis_map ();
		}
	}

	private Gee.HashMap<string, string>? gen_emojis_map () {
		var res = new Gee.HashMap<string, string> ();
		if (emojis != null && emojis.size > 0) {
			emojis.@foreach (e => {
				res.set (e.shortcode, e.url);
				return true;
			});
		}

		return res;
	}

	public Gee.ArrayList<API.EmojiReaction>? compat_status_reactions {
		get {
			if (emoji_reactions != null) {
				return emoji_reactions;
			} else if (pleroma != null && pleroma.emoji_reactions != null) {
				return pleroma.emoji_reactions;
			}

			return reactions;
		}
	}

	public bool compat_local_only {
		get {
			if (pleroma != null && visibility == "local") {
				return true;
			}

			return this.local_only;
		}
	}

	public string? t_url { get; set; }
	public string? url {
		owned get { return this.get_modified_url (); }
		set { this.t_url = value; }
	}
	string? get_modified_url () {
		if (this.t_url == null) {
			if (this.uri == null) return null;
			return this.uri.replace (@"$id/activity", id);
		}
		return this.t_url;
	}

	public bool is_edited {
		get { return edited_at != null; }
	}

	public Status formal {
		get { return reblog ?? this; }
	}

	public bool has_spoiler {
		get {
			return !(formal.spoiler_text == null || formal.spoiler_text == "");
		}
	}

	public bool can_be_quoted {
		get {
			if (
				(InstanceAccount.InstanceFeatures.QUOTE in accounts.active.tuba_instance_features || accounts.active.tuba_api_versions.mastodon >= 7)
				&& this.formal.quote_approval != null
			) {
				return this.formal.quote_approval.current_user != "denied" && this.formal.quote_approval.current_user != "unknown";
			}

			return this.formal.visibility != "direct" && this.formal.visibility != "private" && this.formal.visibility != "local";
		}
	}

	public bool can_be_boosted {
		get {
			return this.formal.visibility != "direct"
				&& (
					this.formal.visibility != "private"
					|| this.formal.account.is_self ()
				);
		}
	}

	public static Status from (Json.Node node) throws Error {
		return Entity.from_json (typeof (API.Status), node) as API.Status;
	}

	public Status.empty () {
		Object (
			id: ""
		);
	}

	public Status.from_account (API.Account account) {
		Object (
			id: "",
			account: account,
			created_at: account.created_at,
			emojis: account.emojis
		);

		if (account.note == "")
			content = "";
		else if ("\n" in account.note)
			content = account.note.split ("\n")[0];
		else
			content = account.note;
	}

	public override Gtk.Widget to_widget () {
		return new Widgets.Status (this);
	}

	public override void open () {
		app.main_window.open_view (new Views.Thread (formal));
	}

	public bool is_mine {
		get {
			return formal.account.id == accounts.active.id;
		}
	}

	public bool has_media {
		get {
			return media_attachments != null && !media_attachments.is_empty;
		}
	}

	public virtual bool get_reply_mentions (out string joined_mentions) {
		string[] result = {};
		if (account.acct != accounts.active.acct)
			result += account.handle;

		if (mentions != null) {
			foreach (var mention in mentions) {
				var equals_current = mention.acct == accounts.active.acct;
				var already_mentioned = mention.handle in result;

				if (!equals_current && !already_mentioned)
					result += mention.handle;
			}
		}

		joined_mentions = string.joinv (" ", result);
		return result.length > 0;
	}

	private RequestV2 action (string action) {
		var req = new RequestV2 (@"/api/v1/statuses/$(formal.id)/$action", POST) { account = accounts.active };
		req.priority = Soup.MessagePriority.HIGH;
		return req;
	}

	public RequestV2 favourite_req () {
		return action ("favourite");
	}

	public RequestV2 unfavourite_req () {
		return action ("unfavourite");
	}

	public RequestV2 bookmark_req () {
		return action ("bookmark");
	}

	public RequestV2 unbookmark_req () {
		return action ("unbookmark");
	}

	public enum QuotePolicy {
		PUBLIC,
		FOLLOWERS,
		NOBODY;

		public string to_string () {
			switch (this) {
				case PUBLIC:
					return "public";
				case FOLLOWERS:
					return "followers";
				default:
					return "nobody";
			}
		}

		public string to_title () {
			switch (this) {
				case PUBLIC:
					// translators: quote policy title, who can quote a post
					return _("Anybody");
				case FOLLOWERS:
					// translators: quote policy title, who can quote a post
					return _("Followers Only");
				default:
					// translators: quote policy title, who can quote a post
					return _("Just Me");
			}
		}

		public string to_subtitle () {
			switch (this) {
				case PUBLIC:
					// translators: quote policy subtitle, when Anybody can quote
					return _("Everyone can quote your post");
				case FOLLOWERS:
					// translators: quote policy subtitle, when Followers Only can quote
					return _("Only your followers can quote your post");
				default:
					// translators: quote policy subtitle, when Just Me can quote
					return _("Nobody can quote your post");
			}
		}

		public static QuotePolicy from_string (string policy) {
			switch (policy.down ()) {
				case "public": return PUBLIC;
				case "followers": return FOLLOWERS;
				default: return NOBODY;
			}
		}
	}

	public enum Visibility {
		PUBLIC,
		UNLISTED,
		PRIVATE,
		DIRECT,
		LOCAL;

		public string to_string () {
			switch (this) {
				case PUBLIC:
					return "public";
				case UNLISTED:
					return "unlisted";
				case PRIVATE:
					return "private";
				case DIRECT:
					return "direct";
				case LOCAL:
					return "local";
				default:
					assert_not_reached ();
			}
		}

		public string to_title () {
			switch (this) {
				case PUBLIC:
					// translators: post visibility label
					return _("Public");
				case UNLISTED:
					// translators: Probably follow Mastodon's translation
					// 				post visibility label
					return _("Unlisted");
				case PRIVATE:
					// translators: post visibility label
					return _("Followers Only");
				case DIRECT:
					// translators: post visibility label
					return _("Direct");
				case LOCAL:
					// translators: post visibility label
					//				local = will be displayed in this instance only
					return _("Local");
				default:
					assert_not_reached ();
			}
		}

		public static Visibility? from_string (string id) {
			switch (id.down ()) {
				case "public":
					return PUBLIC;
				case "unlisted":
					return UNLISTED;
				case "private":
					return PRIVATE;
				case "direct":
					return DIRECT;
				case "local":
					return LOCAL;
				default:
					return null;
			}
		}

		public int privacy_rate () {
			switch (this) {
				case PUBLIC:
					return 0;
				case UNLISTED:
					return 1;
				case PRIVATE:
					return 2;
				case DIRECT:
					return 3;
				case LOCAL: // it's not actually more private but it should take priority
					return 4;
				default:
					assert_not_reached ();
			}
		}
	}

	public RequestV2 reblog_req (Visibility? visibility = null) {
		var req = action ("reblog");
		if (visibility != null)
			req.add_form_data ("visibility", visibility.to_string ());

		return req;
	}

	public RequestV2 unreblog_req () {
		return action ("unreblog");
	}

	public RequestV2 annihilate () {
		return new RequestV2 (@"/api/v1/statuses/$id", DELETE) { account = accounts.active };
	}
}
