text: 'use strict';
  const {hasSideEffect} = require('eslint-utils');
  const {fixSpaceAroundKeyword} = require('./fix/index.js');
  
  const ERROR_BITWISE = 'error-bitwise';
  const ERROR_BITWISE_NOT = 'error-bitwise-not';
  const SUGGESTION_BITWISE = 'suggestion-bitwise';
  const messages = {
  	[ERROR_BITWISE]: 'Use `Math.trunc` instead of `{{operator}} {{value}}`.',
  	[ERROR_BITWISE_NOT]: 'Use `Math.trunc` instead of `~~`.',
  	[SUGGESTION_BITWISE]: 'Replace `{{operator}} {{value}}` with `Math.trunc`.',
  };
  
  const createBitwiseNotSelector = (level, isNegative) => {
  	const prefix = 'argument.'.repeat(level);
  	const selector = [
  		`[${prefix}type="UnaryExpression"]`,
  		`[${prefix}operator="~"]`,
  	].join('');
  	return isNegative ? `:not(${selector})` : selector;
  };
  
  // Bitwise operators
  const bitwiseOperators = new Set(['|', '>>', '<<', '^']);
  // Unary Expression Selector: Inner-most 2 bitwise NOT
  const bitwiseNotUnaryExpressionSelector = [
  	createBitwiseNotSelector(0),
  	createBitwiseNotSelector(1),
  	createBitwiseNotSelector(2, true),
  ].join('');
  
  const create = context => {
  	const sourceCode = context.getSourceCode();
  
  	const mathTruncFunctionCall = node => {
  		const text = sourceCode.getText(node);
  		const parenthesized = node.type === 'SequenceExpression' ? `(${text})` : text;
  		return `Math.trunc(${parenthesized})`;
  	};
  
  	return {
  		':matches(BinaryExpression, AssignmentExpression)[right.type="Literal"]': node => {
  			const {type, operator, right, left} = node;
  			const isAssignment = type === 'AssignmentExpression';
  			if (
  				right.value !== 0 ||
  				!bitwiseOperators.has(isAssignment ? operator.slice(0, -1) : operator)
  			) {
  				return;
  			}
  
  			const problem = {
  				node,
  				messageId: ERROR_BITWISE,
  				data: {
  					operator,
  					value: right.raw,
  				},
  			};
  
  			if (!isAssignment || !hasSideEffect(left, sourceCode)) {
  				const fix = function * (fixer) {
  					let fixed = mathTruncFunctionCall(left);
  					if (isAssignment) {
  						// TODO[@fisker]: Improve this fix, don't touch left
  						fixed = `${sourceCode.getText(left)} = ${fixed}`;
  					} else {
  						yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  					}
  
  					yield fixer.replaceText(node, fixed);
  				};
  
  				if (operator === '|') {
  					problem.suggest = [
  						{
  							messageId: SUGGESTION_BITWISE,
  							data: {
  								operator,
  								value: right.raw,
  							},
  							fix,
  						},
  					];
  				} else {
  					problem.fix = fix;
  				}
  			}
  
  			return problem;
  		},
  		[bitwiseNotUnaryExpressionSelector]: node => {
  			return {
  				node,
  				messageId: ERROR_BITWISE_NOT,
  				* fix(fixer) {
  					yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
  					yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  				},
  			};
  		},
  	};
  };
  
  module.exports = {
  	create,
  	meta: {
  		type: 'suggestion',
  		docs: {
  			description: 'Enforce the use of `Math.trunc` instead of bitwise operators.',
  		},
  		fixable: 'code',
  		messages,
  		hasSuggestions: true,
  	},
  };
  