'use strict';

var test = require('tape');
var forEach = require('for-each');
var debug = require('object-inspect');
var v = require('es-value-fixtures');

var CompletionRecord = require('es-abstract/2025/CompletionRecord');
var NormalCompletion = require('es-abstract/2025/NormalCompletion');
var ReturnCompletion = require('es-abstract/2025/ReturnCompletion');
var ThrowCompletion = require('es-abstract/2025/ThrowCompletion');

var SLOT = require('internal-slot');
var IteratorCloseAll = require('../aos/IteratorCloseAll');
var IfAbruptCloseIterators = require('../aos/IfAbruptCloseIterators');
var GetOptionsObject = require('../aos/GetOptionsObject');
var GeneratorResumeAbrupt = require('../aos/GeneratorResumeAbrupt');

var makeGenerator = function (state) {
	var gen = {};
	SLOT.set(gen, '[[GeneratorState]]', state);
	SLOT.set(gen, '[[GeneratorBrand]]', 'test');
	SLOT.set(gen, '[[GeneratorContext]]', null);
	SLOT.set(gen, '[[CloseIfAbrupt]]', null);
	return gen;
};

test('GeneratorResumeAbrupt', function (t) {
	t.test('ThrowCompletion on SUSPENDED-START state', function (st) {
		var gen = makeGenerator('SUSPENDED-START');

		st['throws'](
			function () { GeneratorResumeAbrupt(gen, ThrowCompletion(new EvalError('test')), 'test'); },
			EvalError,
			'throw completion re-throws on SUSPENDED-START (transitions to COMPLETED first)'
		);
		st.equal(SLOT.get(gen, '[[GeneratorState]]'), 'COMPLETED', 'state is COMPLETED after throw');
		st.end();
	});

	t.test('ThrowCompletion on COMPLETED state', function (st) {
		var gen = makeGenerator('COMPLETED');

		st['throws'](
			function () { GeneratorResumeAbrupt(gen, ThrowCompletion(new EvalError('test')), 'test'); },
			EvalError,
			'throw completion re-throws on COMPLETED'
		);
		st.end();
	});

	t.test('ReturnCompletion on COMPLETED state', function (st) {
		var gen = makeGenerator('COMPLETED');

		var result = GeneratorResumeAbrupt(gen, ReturnCompletion(42), 'test');
		st.deepEqual(result, { value: 42, done: true }, 'return completion returns {value, done: true}');
		st.end();
	});

	t.test('ThrowCompletion on SUSPENDED-YIELD calls genContext', function (st) {
		var gen = makeGenerator('SUSPENDED-YIELD');
		var contextCalledWith = null;
		SLOT.set(gen, '[[GeneratorContext]]', function (val) {
			contextCalledWith = val;
			return { value: undefined, done: true };
		});

		var result = GeneratorResumeAbrupt(gen, ThrowCompletion(new EvalError('injected')), 'test');
		st.ok(contextCalledWith instanceof EvalError, 'genContext called with the error value');
		st.deepEqual(result, { value: undefined, done: true }, 'returns genContext result');
		st.end();
	});

	t.test('ReturnCompletion on SUSPENDED-YIELD calls closeIfAbrupt', function (st) {
		var gen = makeGenerator('SUSPENDED-YIELD');
		var closeCalledWith = null;
		SLOT.set(gen, '[[GeneratorContext]]', function () { return { value: undefined, done: true }; });
		SLOT.set(gen, '[[CloseIfAbrupt]]', function (completion) {
			closeCalledWith = completion;
		});

		var result = GeneratorResumeAbrupt(gen, ReturnCompletion(99), 'test');
		st.ok(closeCalledWith instanceof CompletionRecord, 'closeIfAbrupt called with CompletionRecord');
		st.equal(closeCalledWith.type(), 'return', 'completion is a return');
		st.equal(closeCalledWith.value(), 99, 'completion value is 99');
		st.deepEqual(result, { value: 99, done: true }, 'returns {value: 99, done: true}');
		st.equal(SLOT.get(gen, '[[GeneratorState]]'), 'COMPLETED', 'state is COMPLETED');
		st.end();
	});

	t.end();
});

test('GetOptionsObject', function (t) {
	t.test('undefined returns null-prototype object', function (st) {
		var result = GetOptionsObject(void undefined);
		st.equal(typeof result, 'object', 'returns an object');
		st.equal(Object.getPrototypeOf(result), null, 'prototype is null');
		st.end();
	});

	t.test('object returns itself', function (st) {
		var obj = { a: 1 };
		st.equal(GetOptionsObject(obj), obj, 'returns the same object');
		st.end();
	});

	t.test('non-object, non-undefined throws TypeError', function (st) {
		forEach(v.primitives, function (primitive) {
			if (typeof primitive !== 'undefined') {
				st['throws'](
					function () { GetOptionsObject(primitive); },
					TypeError,
					debug(primitive) + ' throws TypeError'
				);
			}
		});
		st.end();
	});

	t.end();
});

test('IfAbruptCloseIterators', function (t) {
	t.test('normal completion returns value', function (st) {
		var result = IfAbruptCloseIterators(NormalCompletion(42), []);
		st.equal(result, 42, 'returns unwrapped value');
		st.end();
	});

	t.test('throw completion closes iterators and throws', function (st) {
		var closeCalled = false;
		var iterRecord = {
			'[[Iterator]]': {
				next: function () { return { done: true }; },
				'return': function () { closeCalled = true; return { done: true }; }
			},
			'[[NextMethod]]': function () { return { done: true }; },
			'[[Done]]': false
		};

		st['throws'](
			function () { IfAbruptCloseIterators(ThrowCompletion(new EvalError('test')), [iterRecord]); },
			EvalError,
			'throws the error'
		);
		st.equal(closeCalled, true, 'iterator was closed');
		st.end();
	});

	t.test('non-CompletionRecord throws TypeError', function (st) {
		st['throws'](
			function () { IfAbruptCloseIterators(42, []); },
			TypeError,
			'non-CompletionRecord throws'
		);
		st.end();
	});

	t.end();
});

test('IteratorCloseAll', function (t) {
	t.test('closes all iterators in reverse order', function (st) {
		var closed = [];
		var makeIter = function (name) {
			return {
				'[[Iterator]]': {
					next: function () { return { done: true }; },
					'return': function () { closed.push(name); return { done: true }; }
				},
				'[[NextMethod]]': function () { return { done: true }; },
				'[[Done]]': false
			};
		};

		st['throws'](
			function () { IteratorCloseAll([makeIter('a'), makeIter('b'), makeIter('c')], ThrowCompletion(new EvalError('test'))); },
			EvalError,
			'throws the completion'
		);
		st.deepEqual(closed, ['c', 'b', 'a'], 'closed in reverse order');
		st.end();
	});

	t.test('close error replaces normal completion', function (st) {
		var iter = {
			'[[Iterator]]': {
				next: function () { return { done: true }; },
				'return': function () { throw new SyntaxError('close error'); }
			},
			'[[NextMethod]]': function () { return { done: true }; },
			'[[Done]]': false
		};

		st['throws'](
			function () { IteratorCloseAll([iter], NormalCompletion(void undefined)); },
			SyntaxError,
			'close error replaces normal completion'
		);
		st.end();
	});

	t.test('original throw completion wins over close error', function (st) {
		var iter = {
			'[[Iterator]]': {
				next: function () { return { done: true }; },
				'return': function () { throw new SyntaxError('close error'); }
			},
			'[[NextMethod]]': function () { return { done: true }; },
			'[[Done]]': false
		};

		st['throws'](
			function () { IteratorCloseAll([iter], ThrowCompletion(new EvalError('original'))); },
			EvalError,
			'original throw completion takes precedence'
		);
		st.end();
	});

	t.test('normal completion after successful close returns undefined', function (st) {
		var iter = {
			'[[Iterator]]': {
				next: function () { return { done: true }; },
				'return': function () { return { done: true }; }
			},
			'[[NextMethod]]': function () { return { done: true }; },
			'[[Done]]': false
		};

		var result = IteratorCloseAll([iter], NormalCompletion(void undefined));
		st.equal(result, void undefined, 'returns undefined');
		st.end();
	});

	t.test('validation', function (st) {
		st['throws'](
			function () { IteratorCloseAll('not an array', NormalCompletion(void undefined)); },
			TypeError,
			'non-array iters throws'
		);
		st['throws'](
			function () { IteratorCloseAll([], 'not a completion'); },
			TypeError,
			'non-CompletionRecord throws'
		);
		st.end();
	});

	t.end();
});