import { parse } from 'acorn';
import { decode } from 'sourcemap-codec';
import keys from './keys.js';
import Program from './Program.js';

export default function check ( magicString, ast ) {
	const code = magicString.toString();

	try {
		parse( code, {
			ecmaVersion: 8,
			sourceType: 'module'
		});
	} catch ( err ) {
		const map = magicString.generateMap();
		const { line, column } = err.loc;
		const snippet = code.slice( Math.max( 0, err.pos - 35 ), Math.min( code.length, err.pos + 35 ) );

		const mappings = decode( map.mappings );
		const segments = mappings[ line - 1 ];

		for ( let i = 0; i < segments.length; i += 1 ) {
			const segment = segments[i];
			if ( segment[0] >= column ) {
				const sourceCodeLine = segment[2];
				const sourceCodeColumn = segment[3];

				err.message = `Butternut generated invalid JS: code in source file near (${sourceCodeLine + 1}:${sourceCodeColumn}) became\n...${snippet}...`;

				try {
					const repro = createRepro( magicString.original, ast, sourceCodeLine, sourceCodeColumn );
					if ( repro ) err.repro = repro;
				} catch (err) {
					// do nothing
				}

				throw err;
			}
		}

		throw err;
	}
}

function createRepro ( source, ast, line, column ) {
	const lines = source.split( '\n' );

	let c = 0;
	for ( let i = 0; i < line; i += 1 ) c += lines[i].length + 1;
	c += column;

	let node = zoomIn( ast, c );

	do {
		node = zoomOut( node );

		const slice = source.slice( node.start, node.end );
		const ast = parse( slice, {
			ecmaVersion: 8,
			preserveParens: true,
			sourceType: 'module',
			allowReturnOutsideFunction: true,
			allowReserved: true
		});

		const { code } = new Program( slice, ast, null ).export({});

		try {
			parse( code, {
				ecmaVersion: 8,
				sourceType: 'module',
				allowReturnOutsideFunction: true,
				allowReserved: true
			});
		} catch ( err ) {
			return {
				input: slice,
				output: code
			};
		}
	} while ( node );
}

function zoomIn ( node, c ) {
	if ( !node ) return null;

	if ( c < node.start ) return null;
	if ( c > node.end ) return null;

	const k = keys[ node.type ];
	for ( let i = 0; i < k.length; i += 1 ) {
		const key = k[i];

		if ( Array.isArray( node[key] ) ) {
			const body = node[key];

			for ( let j = 0; j < body.length; j += 1 ) {
				if ( body[j] ) {
					if ( body[j].start > c ) return zoomIn( body[j], body[j].start );

					const child = zoomIn( body[j], c );
					if ( child ) return child;
				}
			}
		} else {
			const child = zoomIn( node[key], c );
			if ( child ) return child;
		}
	}

	return node;
}

function zoomOut ( node ) {
	while ( !/Statement|Declaration/.test( node.parent.type ) ) {
		if ( !node.parent ) return null;
		node = node.parent;
	}

	return node.parent;
}
@@ -580,16 +580,59 @@
 - 1 %5D;%0A%0A
+%09%09let message = err.message;%0A%09%09let repro;%0A%0A
 %09%09for ( 
@@ -824,20 +824,16 @@
 %5D;%0A%0A%09%09%09%09
-err.
 message 
@@ -967,30 +967,12 @@
 .%60;%0A
-%0A
 %09%09%09%09
-try %7B%0A%09%09%09%09%09const 
 repr
@@ -1055,29 +1055,94 @@
  );%0A
+%0A
 %09%09%09%09
-%09if ( repro ) 
+break;%0A%09%09%09%7D%0A%09%09%7D%0A%0A%09%09const err2 = new Error( message );%0A%09%09err2.check = true;%0A%09%09
 err
+2
 .rep
@@ -1159,75 +1159,27 @@
 ;%0A%09%09
-%09%09%7D catch (err) %7B%0A%09%09%09%09%09// do nothing%0A%09%09%09%09%7D%0A%0A%09%09%09%09throw err;%0A%09%09%09%7D%0A%09%09%7D
+err2.output = code;
 %0A%0A%09%09
@@ -1187,16 +1187,17 @@
 hrow err
+2
 ;%0A%09%7D%0A%7D%0A%0A

include more info on check error