bs.util.registerNamespace( 'bs.permissionManager.panel' );

bs.permissionManager.panel.PermissionManager = function ( cfg ) {
	cfg = cfg || {};
	cfg = Object.assign( { expanded: false, padded: false, classes: [ 'bs-permission-manager-inner' ] }, cfg );
	bs.permissionManager.panel.PermissionManager.parent.call( this, cfg );
	this.activeGroup = null;
	this.dirtyState = {};
	this.matrixMode = 'simple';
	this.matrixInstances = {};

	this.disableModeToggle = false;
};

OO.inheritClass( bs.permissionManager.panel.PermissionManager, OO.ui.PanelLayout );

bs.permissionManager.panel.PermissionManager.prototype.init = async function () {
	this.groupSelector = new bs.permissionManager.panel.GroupManager( {
		editable: true,
		classes: [ 'permission-manager-group-selector' ],
		types: [ 'core-minimal', 'implicit', 'custom', 'extension-minimal' ],
		blacklist: [ 'autoconfirmed' ]
	} );

	this.$matrixCnt = $( '<div>' ).addClass( 'permission-manager-matrix' );
	const [ groupResult, permissionResult ] = await Promise.all( [ // eslint-disable-line no-unused-vars
		this.groupSelector.init(),
		this.loadData()
	] );
	this.permissionOriginalData = permissionResult;
	this.permissionRawData = $.extend( true, {}, this.permissionOriginalData );

	this.advancedSwitch = new OO.ui.ToggleSwitchWidget( {
		value: false,
		classes: [ 'permission-manager-advanced-switch' ]
	} );
	this.advancedSwitch.connect( this, { change: 'toggleAdvanced' } );

	this.$matrixHeader = $( '<div>' ).addClass( 'permission-manager-matrix-header' );
	this.$matrixCnt.append( this.$matrixHeader );

	this.$matrixHeader.append( new OO.ui.FieldLayout( this.advancedSwitch, {
		align: 'right',
		label: mw.message( 'bs-permissionmanager-advanced-switch' ).text()
	} ).$element );

	this.matrixInstances.simple = new bs.permissionManager.panel.SimpleMatrix( { manager: this } );
	this.$matrixCnt.append( this.matrixInstances.simple.$element );

	this.$element.append( this.groupSelector.$element, this.$matrixCnt );

	this.groupSelector.connect( this, {
		groupSelected: 'groupSelected'
	} );
	this.groupSelector.selectFirst();
};

bs.permissionManager.panel.PermissionManager.prototype.toggleAdvanced = function ( value ) {
	if ( this.disableModeToggle ) {
		return;
	}
	if ( this.isDirty() ) {
		OO.ui.confirm( mw.message( 'bs-permissionmanager-advanced-switch-confirm' ).text() )
			.done( ( confirmed ) => {
				if ( confirmed ) {
					this.doToggleAdvanced( value );
				} else {
					this.disableModeToggle = true;
					this.advancedSwitch.setValue( !value );
					this.disableModeToggle = false;
				}
			} );
	} else {
		this.doToggleAdvanced( value );
	}
};

bs.permissionManager.panel.PermissionManager.prototype.doToggleAdvanced = function ( value ) {
	this.reset();
	this.matrixMode = value ? 'advanced' : 'simple';
	if ( this.matrixMode === 'advanced' && !this.matrixInstances.hasOwnProperty( 'advanced' ) ) {
		this.matrixInstances.advanced = new bs.permissionManager.panel.GridMatrix( { manager: this } );
		this.$matrixCnt.append( this.matrixInstances.advanced.$element );
	}
	this.matrixInstances.simple.toggle( this.matrixMode === 'simple' );
	this.matrixInstances.advanced.toggle( this.matrixMode === 'advanced' );
	if ( this.activeGroup ) {
		this.groupSelected( this.activeGroup );
	}
};

bs.permissionManager.panel.PermissionManager.prototype.groupSelected = function ( group ) {
	const matrixData = this.makeMatrixData( group );
	this.activeGroup = group;
	this.matrixInstances[ this.matrixMode ].setActiveGroup( group );
	this.matrixInstances[ this.matrixMode ].render( matrixData );
};

bs.permissionManager.panel.PermissionManager.prototype.reset = function () {
	this.permissionRawData = $.extend( true, {}, this.permissionOriginalData );
	this.dirtyState = {};
	this.groupSelector.resetDirty();
	this.matrixInstances[ this.matrixMode ].reset( this.makeMatrixData( this.activeGroup ) );
};

bs.permissionManager.panel.PermissionManager.prototype.isDirty = function () {
	return !$.isEmptyObject( this.dirtyState );
};

bs.permissionManager.panel.PermissionManager.prototype.loadData = async function () {
	return new Promise( ( resolve, reject ) => {
		$.ajax( {
			url: mw.util.wikiScript( 'rest' ) + '/bs-permission-manager/v1/permissions',
			success: function ( data ) {
				resolve( data );
			},
			error: function ( jqXHR, textStatus, errorThrown ) {
				reject( errorThrown );
			}
		} );
	} );
};

bs.permissionManager.panel.PermissionManager.prototype.makeMatrixData = function ( forGroup ) {
	const data = [];

	for ( let i = 0; i < this.permissionRawData.roles.length; i++ ) {
		data.push( this.getMatrixDataForRoleAndGroup( this.permissionRawData.roles[ i ].role, forGroup ) );
	}
	return data;
};

bs.permissionManager.panel.PermissionManager.prototype.getMatrixDataForRoleAndGroup = function ( role, forGroup ) {
	const row = {}, roleData = this.permissionRawData.roles.find( ( r ) => r.role === role );
	row.role = role;
	row.roleHint = roleData.hint;
	row.roleHintHtml = roleData.hintHtml;
	row.global = this.isGlobalExplicitlyAssigned( forGroup, row.role );
	row.global_meta = this.getGlobalMeta( forGroup, row.role ); // eslint-disable-line camelcase
	for ( let j = 0; j < this.permissionRawData.namespaces.length; j++ ) {
		const namespaceInfo = this.permissionRawData.namespaces[ j ];
		row[ 'ns_' + namespaceInfo.id ] = this.isNamespaceAssigned( forGroup, row.role, namespaceInfo.id );
		row[ 'ns_' + namespaceInfo.id + '_meta' ] = this.getNamespaceMeta( forGroup, row.role, namespaceInfo.id, namespaceInfo.name );
	}
	return row;
};

bs.permissionManager.panel.PermissionManager.prototype.isGlobalExplicitlyAssigned = function ( group, role ) {
	return this.permissionRawData.groupRoles.hasOwnProperty( group ) &&
		this.permissionRawData.groupRoles[ group ].hasOwnProperty( role ) &&
		this.permissionRawData.groupRoles[ group ][ role ] === true;
};

bs.permissionManager.panel.PermissionManager.prototype.getGlobalMeta = function ( group, role ) {
	const explicit = this.isGlobalExplicitlyAssigned( group, role );
	const upper = [];
	if ( explicit ) {
		return { assignment: 'explicit' };
	}
	if ( group === 'user' ) {
		upper.push( '*' );
	} else if ( group !== '*' ) {
		upper.push( '*' );
		upper.push( 'user' );
	}
	for ( let i = 0; i < upper.length; i++ ) {
		if ( this.isGlobalExplicitlyAssigned( upper[ i ], role ) ) {
			return { assignment: 'inherit', inheritFrom: upper[ i ] };
		}
	}

	return { assignment: false };
};

bs.permissionManager.panel.PermissionManager.prototype.isNamespaceAssigned = function ( group, role, namespace ) {
	return this.permissionRawData.nsLockdown.hasOwnProperty( namespace ) &&
		this.permissionRawData.nsLockdown[ namespace ].hasOwnProperty( role ) &&
		this.permissionRawData.nsLockdown[ namespace ][ role ].indexOf( group ) !== -1;
};

bs.permissionManager.panel.PermissionManager.prototype.getNamespaceMeta = function ( group, role, namespace, namespaceName ) {
	const meta = { nsId: namespace, nsName: namespaceName };
	const explicit = this.permissionRawData.nsLockdown.hasOwnProperty( namespace ) &&
		this.permissionRawData.nsLockdown[ namespace ].hasOwnProperty( role ) &&
		this.permissionRawData.nsLockdown[ namespace ][ role ].indexOf( group ) !== -1;
	if ( explicit ) {
		meta.assignment = 'explicit';
	} else if ( this.getGlobalMeta( group, role ).assignment !== false ) {
		meta.assignment = 'global';
	} else {
		meta.assignment = false;
		return meta;
	}

	if (
		this.permissionRawData.roleDependencyTree.hasOwnProperty( role ) &&
		Object.keys( this.permissionRawData.roleDependencyTree[ role ] ).length > 0
	) {
		const blockingDependencies = this.getBlockingDependencies(
			role,
			this.permissionRawData.roleDependencyTree[ role ],
			namespace,
			group
		);
		if ( !$.isEmptyObject( blockingDependencies ) ) {
			meta.dependencies = blockingDependencies;
			meta.isBlocked = true;
		}
	}
	meta.blocking = this.getAssignedGroupsForNamespaceAndRole( namespace, role ).filter( ( g ) => g !== group );
	meta.blocking = this.removeImplicitGroupsFromBlock( group, meta.blocking );
	meta.blocking = meta.blocking.map( ( g ) => this.groupSelector.getGroupLabel( g ) || g );
	if ( meta.blocking.length > 0 && meta.assignment !== 'explicit' ) {
		meta.isBlocked = true;
	}
	return meta;
};

bs.permissionManager.panel.PermissionManager.prototype.getBlockingDependencies = function ( role, dependencies, namespace, group ) {
	const res = {};
	for ( const permission in dependencies ) {
		if ( !dependencies.hasOwnProperty( permission ) ) {
			continue;
		}
		const roles = dependencies[ permission ];
		for ( let i = 0; i < roles.length; i++ ) {
			let blocking = this.getAssignedGroupsForNamespaceAndRole( namespace, roles[ i ] );
			blocking = this.removeImplicitGroupsFromBlock( group, blocking );
			if ( blocking.length > 0 && blocking.indexOf( group ) === -1 ) {
				if ( !res.hasOwnProperty( permission ) ) {
					res[ permission ] = [];
				}
				res[ permission ].push( roles[ i ] );
			}
		}
	}
	return res;
};

bs.permissionManager.panel.PermissionManager.prototype.removeImplicitGroupsFromBlock = function ( forGroup, blocking ) {
	blocking = $.extend( [], blocking );
	if ( forGroup !== '*' && blocking.indexOf( '*' ) !== -1 ) {
		// Remove any roles blocked by `*` if the group is not `*`
		blocking.splice( blocking.indexOf( '*' ), 1 );
	}
	if ( forGroup !== 'user' && forGroup !== '*' ) {
		// Remove any roles blocked by `user` and `*` if the group is not `user` or `*`
		if ( blocking.indexOf( 'user' ) !== -1 ) {
			blocking.splice( blocking.indexOf( 'user' ), 1 );
		}
		if ( blocking.indexOf( '*' ) !== -1 ) {
			// Remove `*` if it is present, as it is not relevant for non-user groups
			blocking.splice( blocking.indexOf( '*' ), 1 );
		}
	}

	return blocking;
};

bs.permissionManager.panel.PermissionManager.prototype.getAssignedGroupsForNamespaceAndRole = function ( namespace, role ) {
	const groups = [];
	if ( !this.permissionRawData.nsLockdown.hasOwnProperty( namespace ) ) {
		return groups;
	}
	if ( !this.permissionRawData.nsLockdown[ namespace ].hasOwnProperty( role ) ) {
		return groups;
	}
	return this.permissionRawData.nsLockdown[ namespace ][ role ];
};

bs.permissionManager.panel.PermissionManager.prototype.setGlobal = function ( role, value ) {
	if ( !this.permissionRawData.groupRoles.hasOwnProperty( this.activeGroup ) ) {
		this.permissionRawData.groupRoles[ this.activeGroup ] = {};
	}
	this.permissionRawData.groupRoles[ this.activeGroup ][ role ] = value;
	if ( !value ) {
		// If global permission is removed, remove all NS assignments
		for ( const namespace in this.permissionRawData.nsLockdown ) {
			if ( this.permissionRawData.nsLockdown[ namespace ].hasOwnProperty( role ) ) {
				const index = this.permissionRawData.nsLockdown[ namespace ][ role ].indexOf( this.activeGroup );
				if ( index !== -1 ) {
					this.permissionRawData.nsLockdown[ namespace ][ role ].splice( index, 1 );
				}
			}
		}
	}
};

bs.permissionManager.panel.PermissionManager.prototype.setNamespace = function ( namespace, role, value ) {
	if ( !this.permissionRawData.nsLockdown.hasOwnProperty( namespace ) ) {
		this.permissionRawData.nsLockdown[ namespace ] = {};
	}
	if ( !this.permissionRawData.nsLockdown[ namespace ].hasOwnProperty( role ) ) {
		this.permissionRawData.nsLockdown[ namespace ][ role ] = [];
	}
	const index = this.permissionRawData.nsLockdown[ namespace ][ role ].indexOf( this.activeGroup );
	if ( value && index === -1 ) {
		this.permissionRawData.nsLockdown[ namespace ][ role ].push( this.activeGroup );
	} else if ( !value && index !== -1 ) {
		this.permissionRawData.nsLockdown[ namespace ][ role ].splice( index, 1 );
	}

	if ( value ) {
		// Assign global permission if namespace permission is set
		if ( !this.permissionRawData.groupRoles.hasOwnProperty( this.activeGroup ) ) {
			this.permissionRawData.groupRoles[ this.activeGroup ] = {};
		}
		this.permissionRawData.groupRoles[ this.activeGroup ][ role ] = true;
	}
};

bs.permissionManager.panel.PermissionManager.prototype.setDirty = function ( column, role, dirty ) {
	if ( !this.dirtyState.hasOwnProperty( this.activeGroup ) ) {
		this.dirtyState[ this.activeGroup ] = {};
	}
	if ( !this.dirtyState[ this.activeGroup ].hasOwnProperty( role ) ) {
		this.dirtyState[ this.activeGroup ][ role ] = [];
	}
	if ( dirty ) {
		if ( this.dirtyState[ this.activeGroup ][ role ].indexOf( column ) === -1 ) {
			this.dirtyState[ this.activeGroup ][ role ].push( column );
		}
	} else {
		const index = this.dirtyState[ this.activeGroup ][ role ].indexOf( column );
		if ( index !== -1 ) {
			this.dirtyState[ this.activeGroup ][ role ].splice( index, 1 );
		}
		if ( this.dirtyState[ this.activeGroup ][ role ].length === 0 ) {
			delete this.dirtyState[ this.activeGroup ][ role ];
		}
		if ( Object.keys( this.dirtyState[ this.activeGroup ] ).length === 0 ) {
			delete this.dirtyState[ this.activeGroup ];
		}
	}
	this.groupSelector.setDirty( this.activeGroup, this.dirtyState.hasOwnProperty( this.activeGroup ) );
};

bs.permissionManager.panel.PermissionManager.prototype.save = async function () {
	if ( !this.isDirty() ) {
		return $.Deferred().resolve().promise();
	}
	return this.doSave( {
		groupRoles: this.permissionRawData.groupRoles,
		roleLockdown: this.permissionRawData.nsLockdown
	} );
};

bs.permissionManager.panel.PermissionManager.prototype.doSave = function ( data ) {
	const manager = this;
	return new Promise( ( resolve, reject ) => {
		$.ajax( {
			url: mw.util.wikiScript( 'rest' ) + '/bs-permission-manager/v1/permissions',
			type: 'POST',
			data: JSON.stringify( data ),
			contentType: 'application/json',
			success: function () {
				manager.permissionOriginalData = $.extend( true, {}, manager.permissionRawData );
				manager.reset();
				resolve();
			},
			error: function ( jqXHR, textStatus, errorThrown ) {
				reject( jqXHR.hasOwnProperty( 'responseJSON' ) ? jqXHR.responseJSON : errorThrown );
			}
		} );
	} );
};
