/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var parseJSDoc = require( 'doctrine' ).parse;
var remark = require( 'remark' );
var remarkLint = require( 'remark-lint' );
var remarkPlugin = require( 'remark-lint-no-missing-blank-lines' );
var isObject = require( '@stdlib/assert/is-object' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var findJSDoc = require( '@stdlib/_tools/eslint/utils/find-jsdoc' );


// VARIABLES //

var DOPTS = {
	'sloppy': true,
	'unwrap': true
};
var rule;


// FUNCTIONS //

/**
* Rule to require blank lines between Markdown block nodes in JSDoc descriptions.
*
* @param {Object} context - ESLint context
* @returns {Object} validators
*/
function main( context ) {
	var options;
	var source;
	var config;
	var opts;
	var lint;

	options = context.options[ 0 ];
	opts = {
		'exceptTightLists': false
	};
	if ( hasOwnProp( options, 'exceptTightLists' ) ) {
		opts.exceptTightLists = options.exceptTightLists;
	}

	config = {
		'plugins': [
			remarkLint,
			[ remarkPlugin, [ 'error', opts ] ]
		]
	};
	lint = remark().use( config ).processSync; // eslint-disable-line node/no-sync
	source = context.getSourceCode();

	return {
		'FunctionExpression:exit': validate,
		'FunctionDeclaration:exit': validate,
		'VariableDeclaration:exit': validate,
		'ExpressionStatement:exit': validate
	};

	/**
	* Lints JSDoc descriptions.
	*
	* @private
	* @param {ASTNode} node - AST node
	*/
	function validate( node ) {
		var jsdoc;
		var vfile;
		var ast;

		jsdoc = findJSDoc( source, node );
		if ( isObject( jsdoc ) ) {
			ast = parseJSDoc( jsdoc.value, DOPTS );
			if ( ast.description ) {
				vfile = lint( ast.description );
				if ( vfile.messages.length ) {
					reportErrors( vfile.messages, jsdoc.loc );
				}
			}
		}
	}

	/**
	* Reports Markdown lint errors.
	*
	* @private
	* @param {ObjectArray} errors - Markdown lint errors
	* @param {Object} location - JSDoc location information
	*/
	function reportErrors( errors, location ) {
		var err;
		var msg;
		var loc;
		var i;

		for ( i = 0; i < errors.length; i++ ) {
			err = errors[ i ];
			msg = err.message;
			loc = copyLocationInfo( location );
			loc.start.line = err.location.start.line + 1; // Note: we assume `/**` is on its own line
			loc.start.column = err.location.start.column + 1; // Note: we assume that 1 space separates `*` from JSDoc description content (e.g., `* ## Beep`)
			report( msg, loc );
		}
	}

	/**
	* Copies AST node location info.
	*
	* @private
	* @param {Object} loc - AST node location
	* @returns {Object} copied location info
	*/
	function copyLocationInfo( loc ) {
		return {
			'start': {
				'line': loc.start.line,
				'column': loc.start.column
			},
			'end': {
				'line': loc.end.line,
				'column': loc.end.column
			}
		};
	}

	/**
	* Reports an error message.
	*
	* @private
	* @param {string} msg - error message
	* @param {Object} loc - error location info
	*/
	function report( msg, loc ) {
		context.report({
			'node': null,
			'message': msg,
			'loc': loc
		});
	}
}


// MAIN //

rule = {
	'meta': {
		'docs': {
			'description': 'require blank lines between Markdown block nodes in JSDoc descriptions'
		},
		'schema': [
			{
				'type': 'object'
			}
		]
	},
	'create': main
};


// EXPORTS //

module.exports = rule;
