/*!
 * VisualEditor user interface MWLatexDialog class.
 *
 * @copyright See AUTHORS.txt
 * @license MIT
 */

/**
 * Abstract dialog for inserting and editing different formulas
 * provided by the Math extension.
 *
 * @abstract
 * @class
 * @extends ve.ui.MWExtensionPreviewDialog
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
ve.ui.MWLatexDialog = function VeUiMWLatexDialog( config ) {
	// Parent constructor
	ve.ui.MWLatexDialog.super.call( this, config );
};

/* Inheritance */

OO.inheritClass( ve.ui.MWLatexDialog, ve.ui.MWExtensionPreviewDialog );

/* Static properties */

ve.ui.MWLatexDialog.static.size = 'larger';

ve.ui.MWLatexDialog.static.dir = 'ltr';

ve.ui.MWLatexDialog.static.symbolsModule = null;

/* Methods */

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

	// Layout for the formula inserter (formula tab panel) and options form (options tab panel)
	this.indexLayout = new OO.ui.IndexLayout();

	const formulaTabPanel = new OO.ui.TabPanelLayout( 'formula', {
		label: ve.msg( 'math-visualeditor-mwlatexdialog-card-formula' ),
		padded: true,
		classes: [ 'latex-dialog-formula-panel' ]
	} );
	const optionsTabPanel = new OO.ui.TabPanelLayout( 'options', {
		label: ve.msg( 'math-visualeditor-mwlatexdialog-card-options' ),
		padded: true,
		classes: [ 'latex-dialog-options-panel' ]
	} );

	this.indexLayout.addTabPanels( [
		formulaTabPanel,
		optionsTabPanel
	] );

	// Layout for symbol picker (menu) and input and preview (content)
	this.menuLayout = new OO.ui.MenuLayout( {
		menuPosition: 'bottom',
		classes: [ 've-ui-mwLatexDialog-menuLayout' ]
	} );

	this.previewElement.$element.addClass(
		've-ui-mwLatexDialog-preview'
	);

	this.input = new ve.ui.MWAceEditorWidget( {
		rows: 1, // This will be recalculated later in onWindowManagerResize
		autocomplete: 'live',
		autocompleteWordList: this.constructor.static.autocompleteWordList
	} ).setLanguage( 'latex' );

	this.input.togglePrintMargin( false );

	this.displaySelect = new OO.ui.ButtonSelectWidget( {
		items: [
			new OO.ui.ButtonOptionWidget( {
				data: 'default',
				icon: 'mathematicsDisplayDefault',
				label: ve.msg( 'math-visualeditor-mwlatexinspector-display-default' )
			} ),
			new OO.ui.ButtonOptionWidget( {
				data: 'inline',
				icon: 'mathematicsDisplayInline',
				label: ve.msg( 'math-visualeditor-mwlatexinspector-display-inline' )
			} ),
			new OO.ui.ButtonOptionWidget( {
				data: 'block',
				icon: 'mathematicsDisplayBlock',
				label: ve.msg( 'math-visualeditor-mwlatexinspector-display-block' )
			} )
		]
	} );

	this.idInput = new OO.ui.TextInputWidget();
	this.qidInput = new mw.widgets.MathWbEntitySelector();

	const inputField = new OO.ui.FieldLayout( this.input, {
		align: 'top',
		classes: [ 'latex-dialog-formula-field' ],
		label: ve.msg( 'math-visualeditor-mwlatexdialog-card-formula' )
	} );
	const displayField = new OO.ui.FieldLayout( this.displaySelect, {
		align: 'top',
		classes: [ 'latex-dialog-display-field' ],
		label: ve.msg( 'math-visualeditor-mwlatexinspector-display' )
	} );
	const idField = new OO.ui.FieldLayout( this.idInput, {
		align: 'top',
		classes: [ 'latex-dialog-id-field' ],
		label: ve.msg( 'math-visualeditor-mwlatexinspector-id' )
	} );
	const qidField = new OO.ui.FieldLayout( this.qidInput, {
		align: 'top',
		classes: [ 'latex-dialog-qid-field' ],
		label: ve.msg( 'math-visualeditor-mwlatexinspector-qid' )
	} );

	const formulaPanel = new OO.ui.PanelLayout( {
		scrollable: true,
		padded: true
	} );

	// Layout for the symbol picker
	this.bookletLayout = new ve.ui.SymbolListBookletLayout( {
		classes: [ 've-ui-mwLatexDialog-symbols' ]
	} );
	this.pages = [];
	this.symbolsPromise = mw.loader.using( this.constructor.static.symbolsModule ).then( ( require ) => {
		// eslint-disable-next-line security/detect-non-literal-require
		const symbols = require( this.constructor.static.symbolsModule );
		const symbolData = {};
		for ( const category in symbols ) {
			const symbolList = symbols[ category ].filter( ( symbol ) => {
				if ( symbol.notWorking || symbol.duplicate ) {
					return false;
				}
				const tex = symbol.tex || symbol.insert;
				const classes = [ 've-ui-mwLatexDialog-symbol' ];
				classes.push(
					've-ui-mwLatexSymbol-' + tex.replace( /[^\w]/g, ( c ) => '_' + c.charCodeAt( 0 ) + '_' )
				);
				if ( symbol.width ) {
					// The following classes are used here:
					// * ve-ui-mwLatexDialog-symbol-wide
					// * ve-ui-mwLatexDialog-symbol-wider
					// * ve-ui-mwLatexDialog-symbol-widest
					classes.push( 've-ui-mwLatexDialog-symbol-' + symbol.width );
				}
				if ( symbol.contain ) {
					classes.push( 've-ui-mwLatexDialog-symbol-contain' );
				}
				if ( symbol.largeLayout ) {
					classes.push( 've-ui-mwLatexDialog-symbol-largeLayout' );
				}

				// T366737 - make sure the symbols appear in night mode
				classes.push( 'skin-invert' );

				symbol.label = '';
				symbol.classes = classes;

				return true;
			} );
			symbolData[ category ] = {
				// eslint-disable-next-line mediawiki/msg-doc
				label: ve.msg( category ),
				symbols: symbolList
			};
		}
		this.bookletLayout.setSymbolData( symbolData );
		this.bookletLayout.connect( this, {
			choose: 'onSymbolChoose'
		} );

		// Append everything
		formulaPanel.$element.append(
			this.previewElement.$element,
			inputField.$element
		);
		this.menuLayout.setMenuPanel( this.bookletLayout );
		this.menuLayout.setContentPanel( formulaPanel );

		formulaTabPanel.$element.append(
			this.menuLayout.$element
		);
		optionsTabPanel.$element.append(
			displayField.$element,
			idField.$element,
			qidField.$element
		);

		this.$body
			.addClass( 've-ui-mwLatexDialog-content' )
			.append( this.indexLayout.$element );
	} );

};

/**
 * @inheritdoc
 */
ve.ui.MWLatexDialog.prototype.getSetupProcess = function ( data ) {
	return ve.ui.MWLatexDialog.super.prototype.getSetupProcess.call( this, data )
		.next( () => {
			const attributes = this.selectedNode && this.selectedNode.getAttribute( 'mw' ).attrs,
				display = attributes && attributes.display || 'default',
				id = attributes && attributes.id || '',
				qid = attributes && attributes.qid || '',
				isReadOnly = this.isReadOnly();

			// Populate form
			// TODO: This widget is not readable when disabled
			this.displaySelect.selectItemByData( display ).setDisabled( isReadOnly );
			this.idInput.setValue( id ).setReadOnly( isReadOnly );
			this.qidInput.setValue( qid ).setReadOnly( isReadOnly );

			// Add event handlers
			this.input.on( 'change', this.onChangeHandler );
			this.displaySelect.on( 'choose', this.onChangeHandler );
			this.idInput.on( 'change', this.onChangeHandler );
			this.qidInput.on( 'change', this.onChangeHandler );
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWLatexDialog.prototype.getReadyProcess = function ( data ) {
	mw.hook( 've.ui.MwLatexDialogReadyProcess' ).fire();
	return ve.ui.MWLatexDialog.super.prototype.getReadyProcess.call( this, data )
		.next( () => this.symbolsPromise )
		.next( () => {
			// Resize the input once the dialog has been appended
			this.input.adjustSize( true ).focus().moveCursorToEnd();
			this.getManager().connect( this, { resize: 'onWindowManagerResize' } );
			this.onWindowManagerResize();
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWLatexDialog.prototype.getTeardownProcess = function ( data ) {
	return ve.ui.MWLatexDialog.super.prototype.getTeardownProcess.call( this, data )
		.first( () => {
			this.input.off( 'change', this.onChangeHandler );
			this.displaySelect.off( 'choose', this.onChangeHandler );
			this.idInput.off( 'change', this.onChangeHandler );
			this.qidInput.off( 'change', this.onChangeHandler );
			this.getManager().disconnect( this );
			this.indexLayout.setTabPanel( 'formula' );
			this.indexLayout.resetScroll();
			this.menuLayout.resetScroll();
			this.bookletLayout.resetScroll();
		} );
};

/**
 * @inheritdoc
 */
ve.ui.MWLatexDialog.prototype.updateMwData = function ( mwData ) {
	// Parent method
	ve.ui.MWLatexDialog.super.prototype.updateMwData.call( this, mwData );

	// Get data from dialog
	const display = this.displaySelect.findSelectedItem().getData();
	const id = this.idInput.getValue();
	const qid = this.qidInput.getValue();

	// Update attributes
	mwData.attrs.display = display !== 'default' ? display : undefined;
	mwData.attrs.id = id || undefined;
	mwData.attrs.qid = qid || undefined;
};

/**
 * @inheritdoc
 */
ve.ui.MWLatexDialog.prototype.getBodyHeight = function () {
	return 600;
};

/**
 * Handle the window resize event
 */
ve.ui.MWLatexDialog.prototype.onWindowManagerResize = function () {
	this.input.loadingPromise.always( () => {
		// Toggle short mode as necessary
		// NB a change of mode triggers a transition...
		this.menuLayout.$element.toggleClass(
			've-ui-mwLatexDialog-menuLayout-short', this.menuLayout.$element.height() < 450
		);

		// ...so wait for the possible menuLayout transition to finish
		setTimeout( () => {
			// Give the input the right number of rows to fit the space
			const availableSpace = this.menuLayout.$content.height() - this.input.$element.position().top;
			// TODO: Compute this line height from the skin
			const singleLineHeight = 21;
			const border = 1;
			const padding = 3;
			const borderAndPadding = 2 * ( border + padding );
			const maxInputHeight = availableSpace - borderAndPadding;
			const minRows = Math.floor( maxInputHeight / singleLineHeight );
			this.input.loadingPromise.then( () => {
				this.input.setMinRows( minRows );
			}, () => {
				this.input.$input.attr( 'rows', minRows );
			} );
		}, OO.ui.theme.getDialogTransitionDuration() );
	} );
};

/**
 * Handle a symbol being chosen from the list
 *
 * @param {Object} symbol
 */
ve.ui.MWLatexDialog.prototype.onSymbolChoose = function ( symbol ) {
	if ( this.isReadOnly() ) {
		return;
	}

	const encapsulate = symbol.encapsulate;

	if ( encapsulate ) {
		const range = this.input.getRange();
		if ( range.from === range.to ) {
			this.input.insertContent( encapsulate.placeholder );
			this.input.selectRange( range.from, range.from + encapsulate.placeholder.length );
		}
		this.input.encapsulateContent( encapsulate.pre, encapsulate.post );
	} else {
		const insert = symbol.insert;
		this.input.insertContent( insert );
	}
};
