( function () {
	/**
	 * An inbox-type widget that encompases a dated notifications list with pagination
	 *
	 * @class
	 * @extends OO.ui.Widget
	 * @mixes OO.ui.mixin.PendingElement
	 *
	 * @constructor
	 * @param {mw.echo.Controller} controller Echo controller
	 * @param {mw.echo.dm.ModelManager} manager Model manager
	 * @param {Object} [config] Configuration object
	 * @param {number} [config.limit=25] Limit the number of notifications per page
	 * @param {string} [config.prefLink] Link to preferences page
	 * @param {jQuery} [config.$overlay] An overlay for the popup menus
	 */
	mw.echo.ui.NotificationsInboxWidget = function MwEchoUiNotificationsInboxWidget( controller, manager, config ) {
		config = config || {};

		// Parent constructor
		mw.echo.ui.NotificationsInboxWidget.super.call( this, config );
		// Mixin constructors
		OO.ui.mixin.PendingElement.call( this, config );

		this.controller = controller;
		this.manager = manager;

		this.$overlay = config.$overlay || this.$element;
		this.limit = config.limit || 25;

		this.error = false;

		// A notice or error message widget
		this.noticeMessageWidget = new OO.ui.LabelWidget( {
			classes: [ 'mw-echo-ui-notificationsInboxWidget-notice' ]
		} );

		// Notifications list
		this.datedListWidget = new mw.echo.ui.DatedNotificationsWidget(
			this.controller,
			this.manager,
			{
				$overlay: this.$overlay,
				animateSorting: false
			}
		);
		this.setPendingElement( this.datedListWidget.$element );

		// Pagination
		this.topPaginationWidget = new mw.echo.ui.PaginationWidget(
			this.manager.getPaginationModel(),
			{
				itemsPerPage: this.limit
			}
		);
		this.bottomPaginationWidget = new mw.echo.ui.PaginationWidget(
			this.manager.getPaginationModel(),
			{
				itemsPerPage: this.limit
			}
		);

		// Settings menu
		this.settingsMenu = new mw.echo.ui.SpecialHelpMenuWidget(
			this.manager,
			{
				framed: true,
				prefLink: config.prefLink,
				$overlay: this.$overlay
			}
		);

		// Filter by read state
		this.readStateSelectWidget = new mw.echo.ui.ReadStateButtonSelectWidget();

		// Sidebar filters
		this.xwikiUnreadWidget = new mw.echo.ui.CrossWikiUnreadFilterWidget(
			this.controller,
			this.manager.getFiltersModel()
		);

		// Events
		this.readStateSelectWidget.connect( this, { filter: 'onReadStateFilter' } );
		this.xwikiUnreadWidget.connect( this, { filter: 'onSourcePageFilter' } );
		this.manager.connect( this, {
			modelItemUpdate: 'updatePaginationLabels',
			localCountChange: 'updatePaginationLabels'
		} );
		this.manager.getFiltersModel().connect( this, { update: 'updateReadStateSelectWidget' } );
		this.manager.getPaginationModel().connect( this, { update: 'updatePaginationLabels' } );
		this.topPaginationWidget.connect( this, { change: 'populateNotifications' } );
		this.bottomPaginationWidget.connect( this, { change: 'populateNotifications' } );
		this.settingsMenu.connect( this, { markAllRead: 'onSettingsMarkAllRead' } );

		this.topPaginationWidget.setDisabled( true );
		this.bottomPaginationWidget.setDisabled( true );

		this.$topToolbar =
			$( '<div>' )
				.addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-top' )
				.append(
					$( '<div>' )
						.addClass( 'mw-echo-ui-notificationsInboxWidget-row' )
						.append(
							$( '<div>' )
								.addClass( 'mw-echo-ui-notificationsInboxWidget-cell-placeholder' )
								.append(
									$( '<div>' )
										.addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-readState' )
										.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
										.append( this.readStateSelectWidget.$element )
								),
							$( '<div>' )
								.addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-pagination' )
								.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
								.append( this.topPaginationWidget.$element ),
							mw.user.isTemp() ? '' : $( '<div>' )
								.addClass( 'mw-echo-ui-notificationsInboxWidget-main-toolbar-settings' )
								.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
								.append( this.settingsMenu.$element )
						)
				);

		this.$toolbarWrapper =
			$( '<div>' )
				.addClass( 'mw-echo-ui-notificationsInboxWidget-toolbarWrapper' )
				.append( this.$topToolbar );

		const $sidebar = $( '<div>' )
			.addClass( 'mw-echo-ui-notificationsInboxWidget-sidebar' )
			.append( this.xwikiUnreadWidget.$element );

		const $main = $( '<div>' )
			.addClass( 'mw-echo-ui-notificationsInboxWidget-main' )
			.append(
				this.$toolbarWrapper,
				this.noticeMessageWidget.$element,
				this.datedListWidget.$element
			);

		this.$element
			.addClass( 'mw-echo-ui-notificationsInboxWidget' )
			.append(
				$( '<div>' )
					.addClass( 'mw-echo-ui-notificationsInboxWidget-row' )
					.append(
						$sidebar
							.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' ),
						$main
							.addClass( 'mw-echo-ui-notificationsInboxWidget-cell' )
					)
			);

		this.updateReadStateSelectWidget();
		this.xwikiUnreadWidget.populateSources();
		this.populateNotifications();

	};

	/* Initialization */

	OO.inheritClass( mw.echo.ui.NotificationsInboxWidget, OO.ui.Widget );
	OO.mixinClass( mw.echo.ui.NotificationsInboxWidget, OO.ui.mixin.PendingElement );

	/* Methods */

	/**
	 * Respond to filters update
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.updateReadStateSelectWidget = function () {
		this.readStateSelectWidget
			.findItemFromData( this.manager.getFiltersModel().getReadState() )
			.setSelected( true );
	};

	/**
	 * Update pagination messages
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.updatePaginationLabels = function () {
		this.resetMessageLabel();
		// Update the pagination label
		this.topPaginationWidget.updateLabel();
		this.bottomPaginationWidget.updateLabel();
	};

	/**
	 * Respond to mark all read for selected wiki
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.onSettingsMarkAllRead = function () {
		this.pushPending();
		this.controller.markAllRead()
			.always( this.popPending.bind( this ) );
	};

	/**
	 * Respond to unread page filter
	 *
	 * @param {string} source Source symbolic name
	 * @param {string} page Page name
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.onSourcePageFilter = function ( source, page ) {
		this.controller.setFilter( 'sourcePage', source, page );
		this.populateNotifications();
	};

	/**
	 * Respond to read state filter event
	 *
	 * @param {string} readState Read state 'all', 'read' or 'unread'
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.onReadStateFilter = function ( readState ) {
		this.controller.setFilter( 'readState', readState );
		this.populateNotifications();
	};

	/**
	 * Populate the notifications list
	 *
	 * @param {string} [direction] Direction to fetch from. 'prev' for previous page
	 *  or 'next' for the next page. If not given, the first page of results will be fetched.
	 * @return {jQuery.Promise} A promise that is resolved when the results
	 *  have been fetched.
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.populateNotifications = function ( direction ) {
		let fetchPromise;
		if ( direction === 'prev' ) {
			fetchPromise = this.controller.fetchPrevPageByDate();
		} else if ( direction === 'next' ) {
			fetchPromise = this.controller.fetchNextPageByDate();
		} else {
			fetchPromise = this.controller.fetchFirstPageByDate();
		}

		this.pushPending();
		this.error = false;
		return fetchPromise
			.then(
				// Success
				() => {
					// Fire initialization hook
					mw.hook( 'ext.echo.special.onInitialize' ).fire( this.controller.manager.getTypeString(), this.controller );

					this.popPending();
					// Update seen time
					this.controller.updateSeenTime();
				},
				// Failure
				( errObj ) => {
					let msg;
					if ( errObj.errCode === 'notlogin-required' ) {
						// Login required message
						msg = mw.msg( 'echo-notification-loginrequired' );
					} else {
						// Generic API failure message
						msg = mw.msg( 'echo-api-failure' );
					}
					this.error = true;
					this.noticeMessageWidget.setLabel( msg );
					this.displayMessage( true );
				}
			)
			.always( this.popPending.bind( this ) );
	};

	/**
	 * Extend the pushPending method to disable UI elements
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.pushPending = function () {
		this.noticeMessageWidget.toggle( false );
		this.topPaginationWidget.setDisabled( true );
		this.bottomPaginationWidget.setDisabled( true );

		// Mixin method
		OO.ui.mixin.PendingElement.prototype.pushPending.call( this );
	};

	/**
	 * Extend the popPending method to enable UI elements
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.popPending = function () {
		if ( !this.error ) {
			this.resetMessageLabel();
		}

		this.topPaginationWidget.setDisabled( false );
		this.bottomPaginationWidget.setDisabled( false );

		// Mixin method
		OO.ui.mixin.PendingElement.prototype.popPending.call( this );
	};

	/**
	 * Reset the text of the error message that displays in place of the list
	 * in case the list is empty.
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.resetMessageLabel = function () {
		const count = this.manager.getPaginationModel().getCurrentPageItemCount();

		if ( count === 0 ) {
			const label = this.manager.getFiltersModel().getReadState() === 'all' ?
				mw.msg( 'echo-notification-placeholder' ) :
				mw.msg( 'echo-notification-placeholder-filters' );

			this.noticeMessageWidget.setLabel( label );
		}

		this.displayMessage( count === 0 );
	};

	/**
	 * Display the error/notice message instead of the notifications list or vise versa.
	 *
	 * @private
	 * @param {boolean} displayMessage Display error message
	 */
	mw.echo.ui.NotificationsInboxWidget.prototype.displayMessage = function ( displayMessage ) {
		this.noticeMessageWidget.toggle( displayMessage );
		this.datedListWidget.toggle( !displayMessage );
	};

}() );
