/*!
 * VisualEditor UserInterface MWLiveExtensionInspector class.
 *
 * @copyright See AUTHORS.txt
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * Inspector for editing generic MediaWiki extensions with dynamic rendering.
 *
 * @class
 * @abstract
 * @extends ve.ui.MWExtensionInspector
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
ve.ui.MWLiveExtensionInspector = function VeUiMWLiveExtensionInspector() {
	// Parent constructor
	ve.ui.MWLiveExtensionInspector.super.apply( this, arguments );

	this.updatePreviewDebounced = ve.debounce( this.updatePreview.bind( this ), 1000 );
};

/* Inheritance */

OO.inheritClass( ve.ui.MWLiveExtensionInspector, ve.ui.MWExtensionInspector );

/* Methods */

/**
 * @inheritdoc
 */
ve.ui.MWLiveExtensionInspector.prototype.initialize = function () {
	// Parent method
	ve.ui.MWLiveExtensionInspector.super.prototype.initialize.call( this );

	this.generatedContentsError = new ve.ui.MWExpandableErrorElement();
	this.form.$element.append( this.generatedContentsError.$element );
};

/**
 * @inheritdoc
 */
ve.ui.MWLiveExtensionInspector.prototype.getSetupProcess = function ( data ) {
	return ve.ui.MWLiveExtensionInspector.super.prototype.getSetupProcess.call( this, data )
		.next( () => {
			const element = this.getNewElement();
			// Initialization
			this.getFragment().getSurface().pushStaging();

			if ( !this.selectedNode ) {
				// Create a new node
				// collapseToEnd returns a new fragment
				this.fragment = this.getFragment().collapseToEnd().insertContent( [
					element,
					{ type: '/' + element.type }
				] );
				// Check if the node was inserted at a structural offset and
				// wrapped in a paragraph
				if ( this.getFragment().getSelection().getRange().getLength() === 4 ) {
					this.fragment = this.getFragment().adjustLinearSelection( 1, -1 );
				}
				this.getFragment().select();
				this.selectedNode = this.getFragment().getSelectedNode();
			}
			this.input.on( 'change', this.onChangeHandler );
			this.generatedContentsError.connect( this, {
				update: 'updateSize'
			} );
			this.selectedNode.connect( this, {
				generatedContentsError: 'showGeneratedContentsError'
			} );
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWLiveExtensionInspector.prototype.getTeardownProcess = function ( data ) {
	return ve.ui.MWLiveExtensionInspector.super.prototype.getTeardownProcess.call( this, data )
		.first( () => {
			this.input.off( 'change', this.onChangeHandler );
			this.generatedContentsError.clear();
			this.generatedContentsError.disconnect( this );
			this.selectedNode.disconnect( this );
			if ( data === undefined ) { // cancel
				this.getFragment().getSurface().popStaging();
			}
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWLiveExtensionInspector.prototype.insertOrUpdateNode = function () {
	// No need to call parent method as changes have already been made
	// to the model in staging, just need to apply them.
	this.updatePreview();
	this.getFragment().getSurface().applyStaging();
	// Force the selected node to re-render after staging has finished
	this.selectedNode.emit( 'update', false );
};

/**
 * @inheritdoc
 */
ve.ui.MWLiveExtensionInspector.prototype.removeNode = function () {
	this.getFragment().getSurface().popStaging();

	// Parent method
	ve.ui.MWLiveExtensionInspector.super.prototype.removeNode.call( this );
};

/**
 * @inheritdoc
 */
ve.ui.MWLiveExtensionInspector.prototype.onChange = function () {
	// Parent method
	ve.ui.MWLiveExtensionInspector.super.prototype.onChange.call( this );

	this.updatePreviewDebounced();
};

/**
 * Update the node rendering to reflect the current content in the inspector.
 */
ve.ui.MWLiveExtensionInspector.prototype.updatePreview = function () {
	if ( !this.selectedNode ) {
		// Method is called debounced, so selectedNode may not still exist
		return;
	}
	const mwData = ve.copy( this.selectedNode.getAttribute( 'mw' ) );

	this.updateMwData( mwData );

	this.hideGeneratedContentsError();

	if ( this.visible ) {
		this.getFragment().changeAttributes( { mw: mwData } );
	}
};

/**
 * Show the error container and set the error label to contain the error.
 *
 * @param {jQuery} $element Element containing the error
 */
ve.ui.MWLiveExtensionInspector.prototype.showGeneratedContentsError = function ( $element ) {
	this.generatedContentsError.show( this.formatGeneratedContentsError( $element ) );
};

/**
 * Hide the error and collapse the error container.
 */
ve.ui.MWLiveExtensionInspector.prototype.hideGeneratedContentsError = function () {
	this.generatedContentsError.clear();
};

/**
 * Format the error.
 *
 * Default behaviour returns the error with no modification.
 *
 * @param {jQuery} $element Element containing the error
 * @return {jQuery} $element Element containing the error
 */
ve.ui.MWLiveExtensionInspector.prototype.formatGeneratedContentsError = function ( $element ) {
	return $element;
};