import Response from "./response";
import OAuth from "./oauth";
import proxyAgent, { ProxyConfig } from "./proxy_config";
import Entity from "./entity";
import axios, { AxiosRequestConfig } from "axios";
import Misskey from "./misskey";
import { DEFAULT_UA } from "./default";

export interface WebSocketInterface {
	start(): void;
	stop(): void;
	// EventEmitter
	on(event: string | symbol, listener: (...args: any[]) => void): this;
	once(event: string | symbol, listener: (...args: any[]) => void): this;
	removeListener(
		event: string | symbol,
		listener: (...args: any[]) => void,
	): this;
	removeAllListeners(event?: string | symbol): this;
}

export interface MegalodonInterface {
	/**
	 * Cancel all requests in this instance.
	 *
	 * @return void
	 */
	cancel(): void;

	/**
	 * First, call createApp to get client_id and client_secret.
	 * Next, call generateAuthUrl to get authorization url.
	 * @param client_name Form Data, which is sent to /api/v1/apps
	 * @param options Form Data, which is sent to /api/v1/apps. and properties should be **snake_case**
	 */
	registerApp(
		client_name: string,
		options: Partial<{
			scopes: Array<string>;
			redirect_uris: string;
			website: string;
		}>,
	): Promise<OAuth.AppData>;

	/**
	 * Call /api/v1/apps
	 *
	 * Create an application.
	 * @param client_name your application's name
	 * @param options Form Data
	 */
	createApp(
		client_name: string,
		options: Partial<{
			scopes: Array<string>;
			redirect_uris: string;
			website: string;
		}>,
	): Promise<OAuth.AppData>;

	// ======================================
	// apps
	// ======================================
	/**
	 * GET /api/v1/apps/verify_credentials
	 *
	 * @return An Application
	 */
	verifyAppCredentials(): Promise<Response<Entity.Application>>;

	// ======================================
	// apps/oauth
	// ======================================

	/**
	 * POST /oauth/token
	 *
	 * Fetch OAuth access token.
	 * Get an access token based client_id and client_secret and authorization code.
	 * @param client_id will be generated by #createApp or #registerApp
	 * @param client_secret will be generated by #createApp or #registerApp
	 * @param code will be generated by the link of #generateAuthUrl or #registerApp
	 * @param redirect_uri must be the same uri as the time when you register your OAuth application
	 */
	fetchAccessToken(
		client_id: string | null,
		client_secret: string,
		code: string,
		redirect_uri?: string,
	): Promise<OAuth.TokenData>;

	/**
	 * POST /oauth/token
	 *
	 * Refresh OAuth access token.
	 * Send refresh token and get new access token.
	 * @param client_id will be generated by #createApp or #registerApp
	 * @param client_secret will be generated by #createApp or #registerApp
	 * @param refresh_token will be get #fetchAccessToken
	 */
	refreshToken(
		client_id: string,
		client_secret: string,
		refresh_token: string,
	): Promise<OAuth.TokenData>;

	/**
	 * POST /oauth/revoke
	 *
	 * Revoke an OAuth token.
	 * @param client_id will be generated by #createApp or #registerApp
	 * @param client_secret will be generated by #createApp or #registerApp
	 * @param token will be get #fetchAccessToken
	 */
	revokeToken(
		client_id: string,
		client_secret: string,
		token: string,
	): Promise<Response<{}>>;

	// ======================================
	// accounts
	// ======================================
	/**
	 * POST /api/v1/accounts
	 *
	 * @param username Username for the account.
	 * @param email Email for the account.
	 * @param password Password for the account.
	 * @param agreement Whether the user agrees to the local rules, terms, and policies.
	 * @param locale The language of the confirmation email that will be sent
	 * @param reason Text that will be reviewed by moderators if registrations require manual approval.
	 * @return An account token.
	 */
	registerAccount(
		username: string,
		email: string,
		password: string,
		agreement: boolean,
		locale: string,
		reason?: string | null,
	): Promise<Response<Entity.Token>>;
	/**
	 * GET /api/v1/accounts/verify_credentials
	 *
	 * @return Account.
	 */
	verifyAccountCredentials(): Promise<Response<Entity.Account>>;
	/**
	 * PATCH /api/v1/accounts/update_credentials
	 *
	 * @return An account.
	 */
	updateCredentials(options?: {
		discoverable?: boolean;
		bot?: boolean;
		display_name?: string;
		note?: string;
		avatar?: string;
		header?: string;
		locked?: boolean;
		source?: {
			privacy?: string;
			sensitive?: boolean;
			language?: string;
		};
		fields_attributes?: Array<{ name: string; value: string }>;
	}): Promise<Response<Entity.Account>>;
	/**
	 * GET /api/v1/accounts/:id
	 *
	 * @param id The account ID.
	 * @return An account.
	 */
	getAccount(id: string): Promise<Response<Entity.Account>>;
	/**
   * GET /api/v1/accounts/:id/statuses
   *
   * @param id The account ID.

   * @param options.limit Max number of results to return. Defaults to 20.
   * @param options.max_id Return results older than ID.
   * @param options.since_id Return results newer than ID but starting with most recent.
   * @param options.min_id Return results newer than ID.
   * @param options.pinned Return statuses which include pinned statuses.
   * @param options.exclude_replies Return statuses which exclude replies.
   * @param options.exclude_reblogs Return statuses which exclude reblogs.
   * @param options.only_media Show only statuses with media attached? Defaults to false.
   * @return Account's statuses.
   */
	getAccountStatuses(
		id: string,
		options?: {
			limit?: number;
			max_id?: string;
			since_id?: string;
			min_id?: string;
			pinned?: boolean;
			exclude_replies?: boolean;
			exclude_reblogs?: boolean;
			only_media?: boolean;
		},
	): Promise<Response<Array<Entity.Status>>>;
	/**
	 * GET /api/v1/pleroma/accounts/:id/favourites
	 *
	 * @param id Target account ID.
	 * @param options.limit Max number of results to return.
	 * @param options.max_id Return results order than ID.
	 * @param options.since_id Return results newer than ID.
	 * @return Array of statuses.
	 */
	getAccountFavourites(
		id: string,
		options?: {
			limit?: number;
			max_id?: string;
			since_id?: string;
		},
	): Promise<Response<Array<Entity.Status>>>;
	/**
	 * POST /api/v1/pleroma/accounts/:id/subscribe
	 *
	 * @param id Target account ID.
	 * @return Relationship.
	 */
	subscribeAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/pleroma/accounts/:id/unsubscribe
	 *
	 * @param id Target account ID.
	 * @return Relationship.
	 */
	unsubscribeAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * GET /api/v1/accounts/:id/followers
	 *
	 * @param id The account ID.
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @return The array of accounts.
	 */
	getAccountFollowers(
		id: string,
		options?: {
			limit?: number;
			max_id?: string;
			since_id?: string;
			get_all?: boolean;
			sleep_ms?: number;
		},
	): Promise<Response<Array<Entity.Account>>>;

	/**
	 * GET /api/v1/accounts/:id/featured_tags
	 *
	 * @param id The account ID.
	 * @return The array of accounts.
	 */
	getAccountFeaturedTags(
		id: string,
	): Promise<Response<Array<Entity.FeaturedTag>>>;

	/**
	 * GET /api/v1/accounts/:id/following
	 *
	 * @param id The account ID.
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @return The array of accounts.
	 */
	getAccountFollowing(
		id: string,
		options?: {
			limit?: number;
			max_id?: string;
			since_id?: string;
			get_all?: boolean;
			sleep_ms?: number;
		},
	): Promise<Response<Array<Entity.Account>>>;
	/**
	 * GET /api/v1/accounts/:id/lists
	 *
	 * @param id The account ID.
	 * @return The array of lists.
	 */
	getAccountLists(id: string): Promise<Response<Array<Entity.List>>>;
	/**
	 * GET /api/v1/accounts/:id/identity_proofs
	 *
	 * @param id The account ID.
	 * @return Array of IdentityProof
	 */
	getIdentityProof(id: string): Promise<Response<Array<Entity.IdentityProof>>>;
	/**
	 * POST /api/v1/accounts/:id/follow
	 *
	 * @param id The account ID.
	 * @param reblog Receive this account's reblogs in home timeline.
	 * @return Relationship
	 */
	followAccount(
		id: string,
		options?: {
			reblog?: boolean;
		},
	): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/unfollow
	 *
	 * @param id The account ID.
	 * @return Relationship
	 */
	unfollowAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/block
	 *
	 * @param id The account ID.
	 * @return Relationship
	 */
	blockAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/unblock
	 *
	 * @param id The account ID.
	 * @return RElationship
	 */
	unblockAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/mute
	 *
	 * @param id The account ID.
	 * @param notifications Mute notifications in addition to statuses.
	 * @return Relationship
	 */
	muteAccount(
		id: string,
		notifications: boolean,
	): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/unmute
	 *
	 * @param id The account ID.
	 * @return Relationship
	 */
	unmuteAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/pin
	 *
	 * @param id The account ID.
	 * @return Relationship
	 */
	pinAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/accounts/:id/unpin
	 *
	 * @param id The account ID.
	 * @return Relationship
	 */
	unpinAccount(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * GET /api/v1/accounts/relationships
	 *
	 * @param id The account ID.
	 * @return Relationship
	 */
	getRelationship(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * Get multiple relationships in one method
	 *
	 * @param ids Array of account IDs.
	 * @return Array of Relationship.
	 */
	getRelationships(
		ids: Array<string>,
	): Promise<Response<Array<Entity.Relationship>>>;
	/**
	 * GET /api/v1/accounts/search
	 *
	 * @param q Search query.
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @return The array of accounts.
	 */
	searchAccount(
		q: string,
		options?: {
			following?: boolean;
			resolve?: boolean;
			limit?: number;
			max_id?: string;
			since_id?: string;
		},
	): Promise<Response<Array<Entity.Account>>>;
	// ======================================
	// accounts/bookmarks
	// ======================================
	/**
	 * GET /api/v1/bookmarks
	 *
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getBookmarks(options?: {
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Status>>>;
	// ======================================
	//  accounts/favourites
	// ======================================
	/**
	 * GET /api/v1/favourites
	 *
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getFavourites(options?: {
		limit?: number;
		max_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Status>>>;
	// ======================================
	// accounts/mutes
	// ======================================
	/**
	 * GET /api/v1/mutes
	 *
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of accounts.
	 */
	getMutes(options?: {
		limit?: number;
		max_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Account>>>;
	// ======================================
	// accounts/blocks
	// ======================================
	/**
	 * GET /api/v1/blocks
	 *
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of accounts.
	 */
	getBlocks(options?: {
		limit?: number;
		max_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Account>>>;
	// ======================================
	// accounts/domain_blocks
	// ======================================
	/**
	 * GET /api/v1/domain_blocks
	 *
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of domain name.
	 */
	getDomainBlocks(options?: {
		limit?: number;
		max_id?: string;
		min_id?: string;
	}): Promise<Response<Array<string>>>;
	/**
	 * POST/api/v1/domain_blocks
	 *
	 * @param domain Domain to block.
	 */
	blockDomain(domain: string): Promise<Response<{}>>;
	/**
	 * DELETE /api/v1/domain_blocks
	 *
	 * @param domain Domain to unblock
	 */
	unblockDomain(domain: string): Promise<Response<{}>>;
	// ======================================
	// accounts/filters
	// ======================================
	/**
	 * GET /api/v1/filters
	 *
	 * @return Array of filters.
	 */
	getFilters(): Promise<Response<Array<Entity.Filter>>>;
	/**
	 * GET /api/v1/filters/:id
	 *
	 * @param id The filter ID.
	 * @return Filter.
	 */
	getFilter(id: string): Promise<Response<Entity.Filter>>;
	/**
	 * POST /api/v1/filters
	 *
	 * @param phrase Text to be filtered.
	 * @param context Array of enumerable strings home, notifications, public, thread, account. At least one context must be specified.
	 * @param options.irreversible Should the server irreversibly drop matching entities from home and notifications?
	 * @param options.whole_word Consider word boundaries?
	 * @param options.expires_in ISO 8601 Datetime for when the filter expires.
	 * @return Filter
	 */
	createFilter(
		phrase: string,
		context: Array<Entity.FilterContext>,
		options?: {
			irreversible?: boolean;
			whole_word?: boolean;
			expires_in?: string;
		},
	): Promise<Response<Entity.Filter>>;
	/**
	 * PUT /api/v1/filters/:id
	 *
	 * @param id The filter ID.
	 * @param phrase Text to be filtered.
	 * @param context Array of enumerable strings home, notifications, public, thread, account. At least one context must be specified.
	 * @param options.irreversible Should the server irreversibly drop matching entities from home and notifications?
	 * @param options.whole_word Consider word boundaries?
	 * @param options.expires_in ISO 8601 Datetime for when the filter expires.
	 * @return Filter
	 */
	updateFilter(
		id: string,
		phrase: string,
		context: Array<Entity.FilterContext>,
		options?: {
			irreversible?: boolean;
			whole_word?: boolean;
			expires_in?: string;
		},
	): Promise<Response<Entity.Filter>>;
	/**
	 * DELETE /api/v1/filters/:id
	 *
	 * @param id The filter ID.
	 * @return Removed filter.
	 */
	deleteFilter(id: string): Promise<Response<Entity.Filter>>;
	// ======================================
	// accounts/reports
	// ======================================
	/**
	 * POST /api/v1/reports
	 *
	 * @param account_id Target account ID.
	 * @param comment Reason of the report.
	 * @param options.status_ids Array of Statuses ids to attach to the report.
	 * @param options.forward If the account is remote, should the report be forwarded to the remote admin?
	 * @return Report
	 */
	report(
		account_id: string,
		comment: string,
		options?: { status_ids?: Array<string>; forward?: boolean },
	): Promise<Response<Entity.Report>>;
	// ======================================
	// accounts/follow_requests
	// ======================================
	/**
	 * GET /api/v1/follow_requests
	 *
	 * @param limit Maximum number of results.
	 * @return Array of account.
	 */
	getFollowRequests(limit?: number): Promise<Response<Array<Entity.Account>>>;
	/**
	 * POST /api/v1/follow_requests/:id/authorize
	 *
	 * @param id Target account ID.
	 * @return Relationship.
	 */
	acceptFollowRequest(id: string): Promise<Response<Entity.Relationship>>;
	/**
	 * POST /api/v1/follow_requests/:id/reject
	 *
	 * @param id Target account ID.
	 * @return Relationship.
	 */
	rejectFollowRequest(id: string): Promise<Response<Entity.Relationship>>;
	// ======================================
	// accounts/endorsements
	// ======================================
	/**
	 * GET /api/v1/endorsements
	 *
	 * @param options.limit Max number of results to return. Defaults to 40.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @return Array of accounts.
	 */
	getEndorsements(options?: {
		limit?: number;
		max_id?: string;
		since_id?: string;
	}): Promise<Response<Array<Entity.Account>>>;
	// ======================================
	// accounts/featured_tags
	// ======================================
	/**
	 * GET /api/v1/featured_tags
	 *
	 * @return Array of featured tag.
	 */
	getFeaturedTags(): Promise<Response<Array<Entity.FeaturedTag>>>;
	/**
	 * POST /api/v1/featured_tags
	 *
	 * @param name Target hashtag name.
	 * @return FeaturedTag.
	 */
	createFeaturedTag(name: string): Promise<Response<Entity.FeaturedTag>>;
	/**
	 * DELETE /api/v1/featured_tags/:id
	 *
	 * @param id Target featured tag id.
	 * @return Empty
	 */
	deleteFeaturedTag(id: string): Promise<Response<{}>>;
	/**
	 * GET /api/v1/featured_tags/suggestions
	 *
	 * @return Array of tag.
	 */
	getSuggestedTags(): Promise<Response<Array<Entity.Tag>>>;
	// ======================================
	// accounts/preferences
	// ======================================
	/**
	 * GET /api/v1/preferences
	 *
	 * @return Preferences.
	 */
	getPreferences(): Promise<Response<Entity.Preferences>>;
	// ======================================
	// accounts/suggestions
	// ======================================
	/**
	 * GET /api/v1/suggestions
	 *
	 * @param limit Maximum number of results.
	 * @return Array of accounts.
	 */
	getSuggestions(limit?: number): Promise<Response<Array<Entity.Account>>>;
	// ======================================
	// accounts/tags
	// ======================================
	getFollowedTags(): Promise<Response<Array<Entity.Tag>>>;
	/**
	 * GET /api/v1/tags/:id
	 *
	 * @param id Target hashtag id.
	 * @return Tag
	 */
	getTag(id: string): Promise<Response<Entity.Tag>>;
	/**
	 * POST /api/v1/tags/:id/follow
	 *
	 * @param id Target hashtag id.
	 * @return Tag
	 */
	followTag(id: string): Promise<Response<Entity.Tag>>;
	/**
	 * POST /api/v1/tags/:id/unfollow
	 *
	 * @param id Target hashtag id.
	 * @return Tag
	 */
	unfollowTag(id: string): Promise<Response<Entity.Tag>>;
	// ======================================
	// statuses
	// ======================================
	/**
	 * POST /api/v1/statuses
	 *
	 * @param status Text content of status.
	 * @param options.media_ids Array of Attachment ids.
	 * @param options.poll Poll object.
	 * @param options.in_reply_to_id ID of the status being replied to, if status is a reply.
	 * @param options.sensitive Mark status and attached media as sensitive?
	 * @param options.spoiler_text Text to be shown as a warning or subject before the actual content.
	 * @param options.visibility Visibility of the posted status.
	 * @param options.scheduled_at ISO 8601 Datetime at which to schedule a status.
	 * @param options.language ISO 639 language code for this status.
	 * @param options.quote_id ID of the status being quoted to, if status is a quote.
	 * @return Status
	 */
	postStatus(
		status: string,
		options?: {
			media_ids?: Array<string>;
			poll?: {
				options: Array<string>;
				expires_in: number;
				multiple?: boolean;
				hide_totals?: boolean;
			};
			in_reply_to_id?: string;
			sensitive?: boolean;
			spoiler_text?: string;
			visibility?: "public" | "unlisted" | "private" | "direct";
			scheduled_at?: string;
			language?: string;
			quote_id?: string;
		},
	): Promise<Response<Entity.Status>>;
	/**
	 * GET /api/v1/statuses/:id
	 *
	 * @param id The target status id.
	 * @return Status
	 */
	getStatus(id: string): Promise<Response<Entity.Status>>;
	/**
     PUT /api/v1/statuses/:id
     *
     * @param id The target status id.
     * @return Status
   */
	editStatus(
		id: string,
		options: {
			status?: string;
			spoiler_text?: string;
			sensitive?: boolean;
			media_ids?: Array<string>;
			poll?: {
				options?: Array<string>;
				expires_in?: number;
				multiple?: boolean;
				hide_totals?: boolean;
			};
		},
	): Promise<Response<Entity.Status>>;
	/**
	 * DELETE /api/v1/statuses/:id
	 *
	 * @param id The target status id.
	 * @return Status
	 */
	deleteStatus(id: string): Promise<Response<{}>>;
	/**
	 * GET /api/v1/statuses/:id/context
	 *
	 * Get parent and child statuses.
	 * @param id The target status id.
	 * @return Context
	 */
	getStatusContext(
		id: string,
		options?: { limit?: number; max_id?: string; since_id?: string },
	): Promise<Response<Entity.Context>>;
	/**
	 * GET /api/v1/statuses/:id/history
	 *
	 * Get status edit history.
	 * @param id The target status id.
	 * @return StatusEdit
	 */
	getStatusHistory(id: string): Promise<Response<Array<Entity.StatusEdit>>>;
	/**
	 * GET /api/v1/statuses/:id/reblogged_by
	 *
	 * @param id The target status id.
	 * @return Array of accounts.
	 */
	getStatusRebloggedBy(id: string): Promise<Response<Array<Entity.Account>>>;
	/**
	 * GET /api/v1/statuses/:id/favourited_by
	 *
	 * @param id The target status id.
	 * @return Array of accounts.
	 */
	getStatusFavouritedBy(id: string): Promise<Response<Array<Entity.Account>>>;
	/**
	 * POST /api/v1/statuses/:id/favourite
	 *
	 * @param id The target status id.
	 * @return Status.
	 */
	favouriteStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/unfavourite
	 *
	 * @param id The target status id.
	 * @return Status.
	 */
	unfavouriteStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/reblog
	 *
	 * @param id The target status id.
	 * @return Status.
	 */
	reblogStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/unreblog
	 *
	 * @param id The target status id.
	 * @return Status.
	 */
	unreblogStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/bookmark
	 *
	 * @param id The target status id.
	 * @return Status.
	 */
	bookmarkStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/unbookmark
	 *
	 * @param id The target status id.
	 * @return Status.
	 */
	unbookmarkStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/mute
	 *
	 * @param id The target status id.
	 * @return Status
	 */
	muteStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/unmute
	 *
	 * @param id The target status id.
	 * @return Status
	 */
	unmuteStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/pin
	 * @param id The target status id.
	 * @return Status
	 */
	pinStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/unpin
	 *
	 * @param id The target status id.
	 * @return Status
	 */
	unpinStatus(id: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/react/:name
	 * @param id The target status id.
	 * @param name The name of the emoji reaction to add.
	 * @return Status
	 */
	reactStatus(id: string, name: string): Promise<Response<Entity.Status>>;
	/**
	 * POST /api/v1/statuses/:id/unreact/:name
	 *
	 * @param id The target status id.
	 * @param name The name of the emoji reaction to remove.
	 * @return Status
	 */
	unreactStatus(id: string, name: string): Promise<Response<Entity.Status>>;
	// ======================================
	// statuses/media
	// ======================================
	/**
	 * POST /api/v2/media
	 *
	 * @param file The file to be attached, using multipart form data.
	 * @param options.description A plain-text description of the media.
	 * @param options.focus Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0.
	 * @return Attachment
	 */
	uploadMedia(
		file: any,
		options?: { description?: string; focus?: string },
	): Promise<Response<Entity.Attachment | Entity.AsyncAttachment>>;
	/**
	 * GET /api/v1/media/:id
	 *
	 * @param id Target media ID.
	 * @return Attachment
	 */
	getMedia(id: string): Promise<Response<Entity.Attachment>>;
	/**
	 * PUT /api/v1/media/:id
	 *
	 * @param id Target media ID.
	 * @param options.file The file to be attached, using multipart form data.
	 * @param options.description A plain-text description of the media.
	 * @param options.focus Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0.
	 * @param options.is_sensitive Whether the media is sensitive.
	 * @return Attachment
	 */
	updateMedia(
		id: string,
		options?: {
			file?: any;
			description?: string;
			focus?: string;
			is_sensitive?: boolean;
		},
	): Promise<Response<Entity.Attachment>>;
	// ======================================
	// statuses/polls
	// ======================================
	/**
	 * GET /api/v1/polls/:id
	 *
	 * @param id Target poll ID.
	 * @return Poll
	 */
	getPoll(id: string): Promise<Response<Entity.Poll>>;
	/**
	 * POST /api/v1/polls/:id/votes
	 *
	 * @param id Target poll ID.
	 * @param choices Array of own votes containing index for each option (starting from 0).
	 * @return Poll
	 */
	votePoll(id: string, choices: Array<number>): Promise<Response<Entity.Poll>>;
	// ======================================
	// statuses/scheduled_statuses
	// ======================================
	/**
	 * GET /api/v1/scheduled_statuses
	 *
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of scheduled statuses.
	 */
	getScheduledStatuses(options?: {
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.ScheduledStatus>>>;
	/**
	 * GET /api/v1/scheduled_statuses/:id
	 *
	 * @param id Target status ID.
	 * @return ScheduledStatus.
	 */
	getScheduledStatus(id: string): Promise<Response<Entity.ScheduledStatus>>;
	/**
	 * PUT /api/v1/scheduled_statuses/:id
	 *
	 * @param id Target scheduled status ID.
	 * @param scheduled_at ISO 8601 Datetime at which the status will be published.
	 * @return ScheduledStatus.
	 */
	scheduleStatus(
		id: string,
		scheduled_at?: string | null,
	): Promise<Response<Entity.ScheduledStatus>>;
	/**
	 * DELETE /api/v1/scheduled_statuses/:id
	 *
	 * @param id Target scheduled status ID.
	 */
	cancelScheduledStatus(id: string): Promise<Response<{}>>;
	// ======================================
	// timelines
	// ======================================
	/**
	 * GET /api/v1/timelines/public
	 *
	 * @param options.only_media Show only statuses with media attached? Defaults to false.
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getPublicTimeline(options?: {
		only_media?: boolean;
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Status>>>;
	/**
	 * GET /api/v1/timelines/public
	 *
	 * @param options.only_media Show only statuses with media attached? Defaults to false.
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getLocalTimeline(options?: {
		only_media?: boolean;
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Status>>>;
	/**
	 * GET /api/v1/timelines/tag/:hashtag
	 *
	 * @param hashtag Content of a #hashtag, not including # symbol.
	 * @param options.local Show only local statuses? Defaults to false.
	 * @param options.only_media Show only statuses with media attached? Defaults to false.
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getTagTimeline(
		hashtag: string,
		options?: {
			local?: boolean;
			only_media?: boolean;
			limit?: number;
			max_id?: string;
			since_id?: string;
			min_id?: string;
		},
	): Promise<Response<Array<Entity.Status>>>;
	/**
	 * GET /api/v1/timelines/home
	 *
	 * @param options.local Show only local statuses? Defaults to false.
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getHomeTimeline(options?: {
		local?: boolean;
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Status>>>;
	/**
	 * GET /api/v1/timelines/list/:list_id
	 *
	 * @param list_id Local ID of the list in the database.
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getListTimeline(
		list_id: string,
		options?: {
			limit?: number;
			max_id?: string;
			since_id?: string;
			min_id?: string;
		},
	): Promise<Response<Array<Entity.Status>>>;
	// ======================================
	// timelines/conversations
	// ======================================
	/**
	 * GET /api/v1/conversations
	 *
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of statuses.
	 */
	getConversationTimeline(options?: {
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
	}): Promise<Response<Array<Entity.Conversation>>>;
	/**
	 * DELETE /api/v1/conversations/:id
	 *
	 * @param id Target conversation ID.
	 */
	deleteConversation(id: string): Promise<Response<{}>>;
	/**
	 * POST /api/v1/conversations/:id/read
	 *
	 * @param id Target conversation ID.
	 * @return Conversation.
	 */
	readConversation(id: string): Promise<Response<Entity.Conversation>>;
	// ======================================
	// timelines/lists
	// ======================================
	/**
	 * GET /api/v1/lists
	 *
	 * @return Array of lists.
	 */
	getLists(): Promise<Response<Array<Entity.List>>>;
	/**
	 * GET /api/v1/lists/:id
	 *
	 * @param id Target list ID.
	 * @return List.
	 */
	getList(id: string): Promise<Response<Entity.List>>;
	/**
	 * POST /api/v1/lists
	 *
	 * @param title List name.
	 * @return List.
	 */
	createList(title: string): Promise<Response<Entity.List>>;
	/**
	 * PUT /api/v1/lists/:id
	 *
	 * @param id Target list ID.
	 * @param title New list name.
	 * @return List.
	 */
	updateList(id: string, title: string): Promise<Response<Entity.List>>;
	/**
	 * DELETE /api/v1/lists/:id
	 *
	 * @param id Target list ID.
	 */
	deleteList(id: string): Promise<Response<{}>>;
	/**
	 * GET /api/v1/lists/:id/accounts
	 *
	 * @param id Target list ID.
	 * @param options.limit Max number of results to return.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @return Array of accounts.
	 */
	getAccountsInList(
		id: string,
		options?: {
			limit?: number;
			max_id?: string;
			since_id?: string;
		},
	): Promise<Response<Array<Entity.Account>>>;
	/**
	 * POST /api/v1/lists/:id/accounts
	 *
	 * @param id Target list ID.
	 * @param account_ids Array of account IDs to add to the list.
	 */
	addAccountsToList(
		id: string,
		account_ids: Array<string>,
	): Promise<Response<{}>>;
	/**
	 * DELETE /api/v1/lists/:id/accounts
	 *
	 * @param id Target list ID.
	 * @param account_ids Array of account IDs to add to the list.
	 */
	deleteAccountsFromList(
		id: string,
		account_ids: Array<string>,
	): Promise<Response<{}>>;
	// ======================================
	// timelines/markers
	// ======================================
	/**
	 * GET /api/v1/markers
	 *
	 * @param timelines Array of timeline names, String enum anyOf home, notifications.
	 * @return Marker or empty object.
	 */
	getMarkers(timeline: Array<string>): Promise<Response<Entity.Marker | {}>>;
	/**
	 * POST /api/v1/markers
	 *
	 * @param options.home Marker position of the last read status ID in home timeline.
	 * @param options.notifications Marker position of the last read notification ID in notifications.
	 * @return Marker.
	 */
	saveMarkers(options?: {
		home?: { last_read_id: string };
		notifications?: { last_read_id: string };
	}): Promise<Response<Entity.Marker>>;
	// ======================================
	// notifications
	// ======================================
	/**
	 * GET /api/v1/notifications
	 *
	 * @param options.limit Max number of results to return. Defaults to 20.
	 * @param options.max_id Return results older than ID.
	 * @param options.since_id Return results newer than ID.
	 * @param options.min_id Return results immediately newer than ID.
	 * @param options.exclude_types Array of types to exclude.
	 * @param options.account_id Return only notifications received from this account.
	 * @return Array of notifications.
	 */
	getNotifications(options?: {
		limit?: number;
		max_id?: string;
		since_id?: string;
		min_id?: string;
		exclude_types?: Array<Entity.NotificationType>;
		account_id?: string;
	}): Promise<Response<Array<Entity.Notification>>>;
	/**
	 * GET /api/v1/notifications/:id
	 *
	 * @param id Target notification ID.
	 * @return Notification.
	 */
	getNotification(id: string): Promise<Response<Entity.Notification>>;
	/**
	 * POST /api/v1/notifications/clear
	 */
	dismissNotifications(): Promise<Response<{}>>;
	/**
	 * POST /api/v1/notifications/:id/dismiss
	 *
	 * @param id Target notification ID.
	 */
	dismissNotification(id: string): Promise<Response<{}>>;
	/**
	 * POST /api/v1/pleroma/notifcations/read
	 *
	 * @param id A single notification ID to read
	 * @param max_id Read all notifications up to this ID
	 * @return Array of notifications
	 */
	readNotifications(options: { id?: string; max_id?: string }): Promise<
		Response<Entity.Notification | Array<Entity.Notification>>
	>;
	// ======================================
	// notifications/push
	// ======================================
	/**
	 * POST /api/v1/push/subscription
	 *
	 * @param subscription[endpoint] Endpoint URL that is called when a notification event occurs.
	 * @param subscription[keys][p256dh] User agent public key. Base64 encoded string of public key of ECDH key using prime256v1 curve.
	 * @param subscription[keys] Auth secret. Base64 encoded string of 16 bytes of random data.
	 * @param data[alerts][follow] Receive follow notifications?
	 * @param data[alerts][favourite] Receive favourite notifications?
	 * @param data[alerts][reblog] Receive reblog notifictaions?
	 * @param data[alerts][mention] Receive mention notifications?
	 * @param data[alerts][poll] Receive poll notifications?
	 * @return PushSubscription.
	 */
	subscribePushNotification(
		subscription: { endpoint: string; keys: { p256dh: string; auth: string } },
		data?: {
			alerts: {
				follow?: boolean;
				favourite?: boolean;
				reblog?: boolean;
				mention?: boolean;
				poll?: boolean;
			};
		} | null,
	): Promise<Response<Entity.PushSubscription>>;
	/**
	 * GET /api/v1/push/subscription
	 *
	 * @return PushSubscription.
	 */
	getPushSubscription(): Promise<Response<Entity.PushSubscription>>;
	/**
	 * PUT /api/v1/push/subscription
	 *
	 * @param data[alerts][follow] Receive follow notifications?
	 * @param data[alerts][favourite] Receive favourite notifications?
	 * @param data[alerts][reblog] Receive reblog notifictaions?
	 * @param data[alerts][mention] Receive mention notifications?
	 * @param data[alerts][poll] Receive poll notifications?
	 * @return PushSubscription.
	 */
	updatePushSubscription(
		data?: {
			alerts: {
				follow?: boolean;
				favourite?: boolean;
				reblog?: boolean;
				mention?: boolean;
				poll?: boolean;
			};
		} | null,
	): Promise<Response<Entity.PushSubscription>>;
	/**
	 * DELETE /api/v1/push/subscription
	 */
	deletePushSubscription(): Promise<Response<{}>>;
	// ======================================
	// search
	// ======================================
	/**
	 * GET /api/v2/search
	 *
	 * @param q The search query.
	 * @param type Enum of search target.
	 * @param options.limit Maximum number of results to load, per type. Defaults to 20. Max 40.
	 * @param options.max_id Return results older than this id.
	 * @param options.min_id Return results immediately newer than this id.
	 * @param options.resolve Attempt WebFinger lookup. Defaults to false.
	 * @param options.following Only include accounts that the user is following. Defaults to false.
	 * @param options.account_id If provided, statuses returned will be authored only by this account.
	 * @param options.exclude_unreviewed Filter out unreviewed tags? Defaults to false.
	 * @return Results.
	 */
	search(
		q: string,
		type: "accounts" | "hashtags" | "statuses",
		options?: {
			limit?: number;
			max_id?: string;
			min_id?: string;
			resolve?: boolean;
			offset?: number;
			following?: boolean;
			account_id?: string;
			exclude_unreviewed?: boolean;
		},
	): Promise<Response<Entity.Results>>;

	// ======================================
	// instance
	// ======================================
	/**
	 * GET /api/v1/instance
	 */
	getInstance(): Promise<Response<Entity.Instance>>;

	/**
	 * GET /api/v1/instance/peers
	 */
	getInstancePeers(): Promise<Response<Array<string>>>;

	/**
	 * GET /api/v1/instance/activity
	 */
	getInstanceActivity(): Promise<Response<Array<Entity.Activity>>>;

	// ======================================
	// instance/trends
	// ======================================
	/**
	 * GET /api/v1/trends
	 *
	 * @param limit Maximum number of results to return. Defaults to 10.
	 */
	getInstanceTrends(
		limit?: number | null,
	): Promise<Response<Array<Entity.Tag>>>;

	// ======================================
	// instance/directory
	// ======================================
	/**
	 * GET /api/v1/directory
	 *
	 * @param options.limit How many accounts to load. Default 40.
	 * @param options.offset How many accounts to skip before returning results. Default 0.
	 * @param options.order Order of results.
	 * @param options.local Only return local accounts.
	 * @return Array of accounts.
	 */
	getInstanceDirectory(options?: {
		limit?: number;
		offset?: number;
		order?: "active" | "new";
		local?: boolean;
	}): Promise<Response<Array<Entity.Account>>>;

	// ======================================
	// instance/custom_emojis
	// ======================================
	/**
	 * GET /api/v1/custom_emojis
	 *
	 * @return Array of emojis.
	 */
	getInstanceCustomEmojis(): Promise<Response<Array<Entity.Emoji>>>;

	// ======================================
	// instance/announcements
	// ======================================
	/**
	 * GET /api/v1/announcements
	 *
	 * @param with_dismissed Include announcements dismissed by the user. Defaults to false.
	 * @return Array of announcements.
	 */
	getInstanceAnnouncements(
		with_dismissed?: boolean | null,
	): Promise<Response<Array<Entity.Announcement>>>;

	/**
	 * POST /api/v1/announcements/:id/dismiss
	 */
	dismissInstanceAnnouncement(id: string): Promise<Response<{}>>;

	// ======================================
	// Emoji reactions
	// ======================================
	createEmojiReaction(
		id: string,
		emoji: string,
	): Promise<Response<Entity.Status>>;
	deleteEmojiReaction(
		id: string,
		emoji: string,
	): Promise<Response<Entity.Status>>;
	getEmojiReactions(id: string): Promise<Response<Array<Entity.Reaction>>>;
	getEmojiReaction(
		id: string,
		emoji: string,
	): Promise<Response<Entity.Reaction>>;

	// ======================================
	// WebSocket
	// ======================================
	userSocket(): WebSocketInterface;
	publicSocket(): WebSocketInterface;
	localSocket(): WebSocketInterface;
	tagSocket(tag: string): WebSocketInterface;
	listSocket(list_id: string): WebSocketInterface;
	directSocket(): WebSocketInterface;
}

export class NoImplementedError extends Error {
	constructor(err?: string) {
		super(err);

		this.name = new.target.name;
		Object.setPrototypeOf(this, new.target.prototype);
	}
}

export class ArgumentError extends Error {
	constructor(err?: string) {
		super(err);

		this.name = new.target.name;
		Object.setPrototypeOf(this, new.target.prototype);
	}
}

export class UnexpectedError extends Error {
	constructor(err?: string) {
		super(err);

		this.name = new.target.name;
		Object.setPrototypeOf(this, new.target.prototype);
	}
}

type Instance = {
	title: string;
	uri: string;
	urls: {
		streaming_api: string;
	};
	version: string;
};

/**
 * Detect SNS type.
 * Now support Mastodon, Pleroma and Pixelfed.
 *
 * @param url Base URL of SNS.
 * @param proxyConfig Proxy setting, or set false if don't use proxy.
 * @return SNS name.
 */
export const detector = async (
	url: string,
	proxyConfig: ProxyConfig | false = false,
): Promise<"mastodon" | "pleroma" | "misskey"> => {
	let options: AxiosRequestConfig = {
		headers: {
			"User-Agent": DEFAULT_UA,
		},
	};
	if (proxyConfig) {
		options = Object.assign(options, {
			httpsAgent: proxyAgent(proxyConfig),
		});
	}
	try {
		const res = await axios.get<Instance>(url + "/api/v1/instance", options);
		if (res.data.version.includes("Pleroma")) {
			return "pleroma";
		} else {
			return "mastodon";
		}
	} catch (err) {
		await axios.post<{}>(url + "/api/meta", {}, options);
		return "misskey";
	}
};

/**
 * Get client for each SNS according to megalodon interface.
 *
 * @param baseUrl hostname or base URL.
 * @param accessToken access token from OAuth2 authorization
 * @param userAgent UserAgent is specified in header on request.
 * @param proxyConfig Proxy setting, or set false if don't use proxy.
 * @return Client instance for each SNS you specified.
 */
const generator = (
	baseUrl: string,
	accessToken: string | null = null,
	userAgent: string | null = null,
	proxyConfig: ProxyConfig | false = false,
): MegalodonInterface =>
	new Misskey(baseUrl, accessToken, userAgent, proxyConfig);

export default generator;
