/*!
 * Collapsing script for Special:LanguageStats in MediaWiki Extension:Translate
 * @author Krinkle <krinklemail (at) gmail (dot) com>
 * @author Niklas Laxström
 * @license GPL-2.0-or-later, CC-BY-SA-3.0
 */

( function () {
	'use strict';

	var $columns;

	/**
	 * Add css class to every other visible row.
	 * It's not possible to do zebra colors with CSS only if there are hidden rows.
	 *
	 * @param {jQuery} $table
	 */
	function doZebra( $table ) {
		$table.find( 'tr:visible:odd' ).toggleClass( 'tux-statstable-even', false );
		$table.find( 'tr:visible:even' ).toggleClass( 'tux-statstable-even', true );
	}

	function addExpanders( $table ) {
		var $metaRows = $( 'tr.AggregateMessageGroup', $table );

		// Quick return
		if ( !$metaRows.length ) {
			return;
		}

		$metaRows.each( function () {
			var $parent = $( this ),
				thisGroupId = $parent.attr( 'data-groupid' ),
				$children = $( 'tr[data-parentgroup="' + thisGroupId + '"]', $table );

			// Only do the collapse stuff if this Meta-group actually has children on this page
			if ( !$children.length ) {
				return;
			}

			// Build toggle link
			var $toggler = $( '<span>' ).addClass( 'groupexpander collapsed' )
				.append(
					'[',
					$( '<a>' )
						.attr( 'href', '#' )
						.text( mw.msg( 'translate-langstats-expand' ) ),
					']'
				)
				.on( 'click', function ( e ) {
					var $el = $( this );
					// Switch the state and toggle the rows
					if ( $el.hasClass( 'collapsed' ) ) {
						$children.removeClass( 'statstable-hide' ).trigger( 'show' );
						doZebra( $table );
						$el.removeClass( 'collapsed' ).addClass( 'expanded' );
						$el.find( '> a' ).text( mw.msg( 'translate-langstats-collapse' ) );
					} else {
						$children.addClass( 'statstable-hide' ).trigger( 'hide' );
						doZebra( $table );
						$el.addClass( 'collapsed' ).removeClass( 'expanded' );
						$el.find( '> a' ).text( mw.msg( 'translate-langstats-expand' ) );
					}

					e.preventDefault();
				} );

			// Add the toggle link to the first cell of the meta group table-row
			$parent.find( ' > td' ).first().append( $toggler );

			// Handle hide/show recursively, so that collapsing parent group
			// hides all sub groups regardless of nesting level
			$parent.on( 'hide show', function ( event ) {
				// Reuse $toggle, $parent and $children from parent scope
				if ( $toggler.hasClass( 'expanded' ) ) {
					$children.trigger( event.type )[ event.type ]();
				}
			} );
		} );

		// Create, bind and append the toggle-all button
		var $allChildRows = $( 'tr[data-parentgroup]', $table );
		var $allTogglesCache = null;
		var $toggleAllButton = $( '<span>' ).addClass( 'collapsed' )
			.append(
				'[',
				$( '<a>' )
					.attr( 'href', '#' )
					.text( mw.msg( 'translate-langstats-expandall' ) ),
				']'
			)
			.on( 'click', function ( e ) {
				var $el = $( this ),
					$allToggles = $allTogglesCache || $( '.groupexpander', $table );

				// Switch the state and toggle the rows
				// and update the local toggles too
				if ( $el.hasClass( 'collapsed' ) ) {
					$allChildRows.removeClass( 'statstable-hide' );
					$el.add( $allToggles ).removeClass( 'collapsed' ).addClass( 'expanded' );
					$el.find( '> a' ).text( mw.msg( 'translate-langstats-collapseall' ) );
					$allToggles.find( '> a' ).text( mw.msg( 'translate-langstats-collapse' ) );
				} else {
					$allChildRows.addClass( 'statstable-hide' );
					$el.add( $allToggles ).addClass( 'collapsed' ).removeClass( 'expanded' );
					$el.find( '> a' ).text( mw.msg( 'translate-langstats-expandall' ) );
					$allToggles.find( '> a' ).text( mw.msg( 'translate-langstats-expand' ) );
				}

				doZebra( $table );
				e.preventDefault();
			} );

		// Initially hide them
		$allChildRows.addClass( 'statstable-hide' );
		doZebra( $table );

		// Add the toggle-all button above the table
		$( '<p>' ).addClass( 'groupexpander-all' ).append( $toggleAllButton ).insertBefore( $table );
	}

	function applySorting( $table ) {
		var sort = {},
			re = /#sortable:(\d+)=(asc|desc)/,
			match = re.exec( location.hash );

		if ( match ) {
			var index = parseInt( match[ 1 ], 10 );
			sort[ index ] = match[ 2 ];
		}
		$table.tablesorter( { sortList: [ sort ] } );

		$table.on( 'sortEnd.tablesorter', function () {
			$table.find( '.headerSortDown, .headerSortUp' ).each( function () {
				var headerIndex = $table.find( 'th' ).index( $( this ) ),
					dir = $( this ).hasClass( 'headerSortUp' ) ? 'asc' : 'desc';
				location.hash = 'sortable:' + headerIndex + '=' + dir;

				doZebra( $table );
			} );
		} );
	}

	function narrowTable( $table, enable ) {
		var labelColumnCount = 1,
			// 0-indexed
			defaultValueColumn = 2;

		if ( $columns === undefined ) {
			$columns = $table.find( 'thead > tr > th ' ).map( function ( index, value ) {
				return value.textContent;
			} );
		}

		var $select = makeValueColumnSelector( $columns, labelColumnCount, defaultValueColumn );
		// Prevent table sorter from making the select inaccessible
		$select.on( 'mousedown click', function ( e ) {
			e.stopPropagation();
		} ).on( 'change', function () {
			showValueColumn( $table, $select, labelColumnCount );
		} );

		if ( enable ) {
			showValueColumn( $table, $select, labelColumnCount );
		} else {
			// Restore original headings
			$table.find( 'thead > tr > th' ).map( function ( index ) {
				return $( this ).text( $columns[ index ] );
			} );
			$table.find( 'tr > *' ).removeClass( 'statstable-hide' );
		}

	}

	function makeValueColumnSelector( headings, skip, def ) {
		var $select = $( '<select>' );

		for ( var i = skip; i < headings.length; i++ ) {
			$( '<option>' )
				.text( headings[ i ] )
				.val( i )
				.prop( 'selected', i === def )
				.appendTo( $select );
		}

		return $select;
	}

	function showValueColumn( $table, $select, skip ) {
		var index = parseInt( $select.val(), 10 );
		var cssQuery = 'th:nth-child(_)'.replace( '_', index + 1 );
		$table.find( cssQuery ).html( $select );

		for ( var i = 0; i < $select.children().length; i++ ) {
			cssQuery = 'tr > *:nth-child(_)'.replace( '_', i + skip + 1 );
			$table.find( cssQuery ).toggleClass( 'statstable-hide', i + skip !== index );
		}
	}

	function activateEntitySelector( $group, $messagePrefix ) {
		// hide the message group and prefix selector
		var $groupContainer = $( '.message-group-selector' );

		// Change the label, and update the for attribute, and remove the click handler
		// which causes the entity selector to become un-responsive when triggered
		$groupContainer
			.find( 'label' )
			.text( mw.msg( 'translate-mgs-group-or-prefix' ) )
			.attr( 'for', 'mw-entity-selector-input' )
			.off( 'click' );

		// Determine what value was set, and set it on the entity selector
		var selectedGroup = $group.find( 'select option:selected' ).text();

		// load the entity selector and set the value
		var entitySelector = getEntitySelector( onEntitySelect );
		if ( selectedGroup ) {
			entitySelector.setValue( selectedGroup );
		} else {
			var selectedMessage = $messagePrefix.val();
			if ( selectedMessage ) {
				entitySelector.setValue( selectedMessage );
			}
		}

		$group.addClass( 'hidden' );
		$group.after( entitySelector.$element );
	}

	function onEntitySelect( selectedItem ) {
		if ( selectedItem.type === 'group' ) {
			$( 'select[name="group"]' ).val( selectedItem.data );
			$( 'input[name="messages"]' ).val( '' );
		} else {
			$( 'input[name="messages"]' ).val( selectedItem.data );
			$( 'select[name="group"]' ).val( '' );
		}
	}

	function onSubmit() {
		var selectedGroupName = $( 'select[name="group"]' ).find( 'option:selected' ).text();
		var selectedMessagePrefix = $( 'input[name="messages"]' ).val();
		var currentVal = $( '.tes-entity-selector' ).find( 'input[type="text"]' ).val();

		// Check if the user has selected an invalid entity.
		if ( currentVal !== selectedGroupName && currentVal !== selectedMessagePrefix ) {
			mw.notify(
				mw.msg( 'translate-mgs-invalid-entity' ),
				{
					type: 'error',
					tag: 'invalid-selection'
				}
			);
			return false;
		}
	}

	function getEntitySelector( onSelect ) {
		var EntitySelector = require( 'ext.translate.entity.selector' );
		return new EntitySelector( {
			onSelect: onSelect,
			entityType: [ 'groups', 'messages' ],
			inputId: 'mw-entity-selector-input'
		} );
	}

	$( function () {
		var $table = $( '.statstable' );

		activateEntitySelector( $( '#group' ), $( 'input[name="messages"]' ) );

		// Sometimes the table is not present on the page
		if ( !$table.length ) {
			return;
		}

		// Calculate absolute minimum table width
		if ( window.ResizeObserver ) {
			$table.css( 'max-width', '1px' );
		}

		applySorting( $table );
		addExpanders( $table );

		$( '#mw-message-group-stats-form' ).on( 'submit', onSubmit );

		if ( !window.ResizeObserver ) {
			return;
		}

		var minimumTableWidth;
		// Hopefully previous stuff have time to render by now to have accurate picture of the width
		( window.requestAnimationFrame || setTimeout )( function () {
			minimumTableWidth = $table.outerWidth();
			$table.css( 'max-width', '' );
		} );

		var isNarrowMode;
		new ResizeObserver( function ( entries ) {
			var shouldCollapse = entries[ 0 ].contentRect.width < minimumTableWidth;
			// Some fudge to avoid flapping
			var shouldExpand = entries[ 0 ].contentRect.width - 20 > minimumTableWidth;

			var newMode;
			if ( isNarrowMode && shouldExpand ) {
				newMode = false;
			} else if ( !isNarrowMode && shouldCollapse ) {
				newMode = true;
			} else {
				newMode = isNarrowMode;
			}

			if ( newMode !== isNarrowMode ) {
				isNarrowMode = newMode;
				narrowTable( $table, isNarrowMode );
			}
		} ).observe( $table.parent().get( 0 ) );
	} );
}() );
