const
	View = require( './View' ),
	util = require( './util' ),
	IconButton = require( './IconButton' );

/**
 * A {@link View} that pops up from the bottom of the screen.
 *
 * @final
 */
class Drawer extends View {
	/**
	 * @param {Object} props
	 * @param {string} [props.className] Additional CSS classes to add
	 * @param {jQuery.Element[]} [props.children] An array of elements to append to
	 * @param {Function} [props.onShow] Callback called before showing the drawer.
	 *  It receives a promise given the show process is asynchronous. This is used in
	 *  production by GrowthExperiments.
	 * @param {Function} [props.onBeforeHide] Callback called before hiding the drawer
	 */
	constructor( props ) {
		super(
			util.extend(
				{
					onBeforeHide: () => {},
					showCollapseIcon: true
				},
				props,
				{ events: util.extend( {
					'click .drawer-container__mask': () => {
						this.hide();
					},
					'click .cancel': ( ev ) => {
						ev.preventDefault();
						this.hide();
					},
					click( ev ) {
						ev.stopPropagation();
					}
				}, props.events ) }
			)
		);
	}

	initialize( props ) {
		this.drawerClassName = props.className || '';
		props.className = 'drawer-container';
		this.collapseIcon = new IconButton( {
			icon: 'expand',
			additionalClassNames: 'cancel',
			label: mw.msg( 'mobile-frontend-drawer-arrow-label' )
		} );
		// in milliseconds
		this.minHideDelay = 100;
		super.initialize( props );
	}

	/**
	 * Shows panel after a slight delay
	 *
	 * @memberof module:mobile.startup/Drawer
	 * @instance
	 * @method
	 * @return {jQuery.Promise} Resolves after onShow is called. Used in tests / GrowthExperiments.
	 */
	show() {
		const d = util.Deferred();
		this.$el.prepend( this.$mask );
		// Force redraw by asking the browser to measure the element's width
		this.$el.width();
		const $drawer = this.$el.find( '.drawer' );
		this.$mask.addClass( 'drawer-container__mask--visible' );
		if ( !$drawer.hasClass( 'visible' ) ) {
			$drawer.addClass( 'visible' );
			// IntersectionObserver doesn't fire for content
			// in drawers, so trigger manually (T361212)
			mw.hook( 'mobileFrontend.loadLazyImages' ).fire( this.$el );
			if ( this.options.onShow ) {
				this.options.onShow( d );
			}
			requestAnimationFrame( () => d.resolve() );
		} else {
			d.resolve();
		}
		return d.promise();
	}

	/**
	 * Hides panel
	 *
	 * @memberof module:mobile.startup/Drawer
	 * @instance
	 * @method
	 * @return {jQuery.Promise} Resolves after finished hiding. Used in tests.
	 */
	hide() {
		const d = util.Deferred();
		const $drawer = this.$el.find( '.drawer' );
		$drawer.removeClass( 'visible' );
		this.$mask.removeClass( 'drawer-container__mask--visible' );
		requestAnimationFrame( () => {
			this.options.onBeforeHide( this );
			// Should really use 'transitionend' event here, but as the
			// parent $drawer element is often detatched as well, this
			// might not fire until the next show animation.
			setTimeout( () => {
				this.$mask.detach();
				d.resolve();
			}, 100 );
		} );
		return d.promise();
	}

	/**
	 * @inheritdoc
	 */
	postRender() {
		this.$mask = util.parseHTML( '<div>' ).addClass( 'drawer-container__mask' );
		const props = this.options,
			// eslint-disable-next-line mediawiki/class-doc
			$drawer = util.parseHTML( '<div>' )
				.addClass( `drawer drawer-container__drawer position-fixed ${ this.drawerClassName }`.trim() );

		if ( props.showCollapseIcon ) {
			// append the collapse icon at the top of the drawer
			$drawer.prepend( this.collapseIcon.$el );
		}

		if ( props.children ) {
			// append children
			$drawer.append( props.children );
		}
		this.$el.append( $drawer );
	}
}

module.exports = Drawer;
