import {Buffer} from 'node:buffer';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {
	foobarString,
	foobarUint8Array,
	foobarBuffer,
	foobarObject,
} from '../helpers/input.js';
import {noopGenerator, getOutputGenerator} from '../helpers/generator.js';

setFixtureDirectory();

const getTypeofGenerator = lines => (objectMode, binary) => ({
	* transform(line) {
		lines.push(Object.prototype.toString.call(line));
		yield '';
	},
	objectMode,
	binary,
});

const assertTypeofChunk = (t, lines, expectedType) => {
	t.deepEqual(lines, [`[object ${expectedType}]`]);
};

// eslint-disable-next-line max-params
const testGeneratorFirstEncoding = async (t, input, encoding, expectedType, objectMode, binary) => {
	const lines = [];
	const subprocess = execa('stdin.js', {stdin: getTypeofGenerator(lines)(objectMode, binary), encoding});
	subprocess.stdin.end(input);
	await subprocess;
	assertTypeofChunk(t, lines, expectedType);
};

test('First generator argument is string with default encoding, with string writes', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', false, false);
test('First generator argument is Uint8Array with default encoding, with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'utf8', 'Uint8Array', false, true);
test('First generator argument is string with encoding "utf16le", with string writes', testGeneratorFirstEncoding, foobarString, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'utf16le', 'String', false, false);
test('First generator argument is Uint8Array with encoding "utf16le", with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'utf16le', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "buffer", with string writes', testGeneratorFirstEncoding, foobarString, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'buffer', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'buffer', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "hex", with string writes', testGeneratorFirstEncoding, foobarString, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'hex', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'hex', 'Uint8Array', false, true);
test('First generator argument can be string with objectMode', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', true, undefined);
test('First generator argument can be string with objectMode, "binary: false"', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', true, false);
test('First generator argument can be string with objectMode, "binary: true"', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', true, true);
test('First generator argument can be objects with objectMode', testGeneratorFirstEncoding, foobarObject, 'utf8', 'Object', true, undefined);
test('First generator argument can be objects with objectMode, "binary: false"', testGeneratorFirstEncoding, foobarObject, 'utf8', 'Object', true, false);
test('First generator argument can be objects with objectMode, "binary: true"', testGeneratorFirstEncoding, foobarObject, 'utf8', 'Object', true, true);

// eslint-disable-next-line max-params
const testGeneratorFirstEncodingSync = (t, input, encoding, expectedType, objectMode, binary) => {
	const lines = [];
	execaSync('stdin.js', {stdin: [[input], getTypeofGenerator(lines)(objectMode, binary)], encoding});
	assertTypeofChunk(t, lines, expectedType);
};

test('First generator argument is string with default encoding, with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', false, false);
test('First generator argument is Uint8Array with default encoding, with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'Uint8Array', false, true);
test('First generator argument is string with encoding "utf16le", with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'utf16le', 'String', false, false);
test('First generator argument is Uint8Array with encoding "utf16le", with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'utf16le', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'buffer', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'buffer', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "hex", with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'hex', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'hex', 'Uint8Array', false, true);
test('First generator argument can be string with objectMode, sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', true, undefined);
test('First generator argument can be string with objectMode, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', true, false);
test('First generator argument can be string with objectMode, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', true, true);
test('First generator argument can be objects with objectMode, sync', testGeneratorFirstEncodingSync, foobarObject, 'utf8', 'Object', true, undefined);
test('First generator argument can be objects with objectMode, "binary: false", sync', testGeneratorFirstEncodingSync, foobarObject, 'utf8', 'Object', true, false);
test('First generator argument can be objects with objectMode, "binary: true", sync', testGeneratorFirstEncodingSync, foobarObject, 'utf8', 'Object', true, true);

const testEncodingIgnored = async (t, encoding) => {
	const input = Buffer.from(foobarString).toString(encoding);
	const subprocess = execa('stdin.js', {stdin: noopGenerator(true)});
	subprocess.stdin.end(input, encoding);
	const {stdout} = await subprocess;
	t.is(stdout, input);
};

test('Write call encoding "utf8" is ignored with objectMode', testEncodingIgnored, 'utf8');
test('Write call encoding "utf16le" is ignored with objectMode', testEncodingIgnored, 'utf16le');
test('Write call encoding "hex" is ignored with objectMode', testEncodingIgnored, 'hex');
test('Write call encoding "base64" is ignored with objectMode', testEncodingIgnored, 'base64');

// eslint-disable-next-line max-params
const testGeneratorNextEncoding = async (t, input, encoding, firstObjectMode, secondObjectMode, expectedType, execaMethod) => {
	const lines = [];
	await execaMethod('noop.js', ['other'], {
		stdout: [
			getOutputGenerator(input)(firstObjectMode),
			getTypeofGenerator(lines)(secondObjectMode),
		],
		encoding,
	});
	assertTypeofChunk(t, lines, expectedType);
};

test('Next generator argument is string with default encoding, with string writes', testGeneratorNextEncoding, foobarString, 'utf8', false, false, 'String', execa);
test('Next generator argument is string with default encoding, with string writes, objectMode first', testGeneratorNextEncoding, foobarString, 'utf8', true, false, 'String', execa);
test('Next generator argument is string with default encoding, with string writes, objectMode both', testGeneratorNextEncoding, foobarString, 'utf8', true, true, 'String', execa);
test('Next generator argument is string with default encoding, with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'utf8', false, false, 'String', execa);
test('Next generator argument is Uint8Array with default encoding, with Uint8Array writes, objectMode first', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, false, 'Uint8Array', execa);
test('Next generator argument is string with default encoding, with Uint8Array writes, objectMode both', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, true, 'Uint8Array', execa);
test('Next generator argument is string with encoding "utf16le", with string writes', testGeneratorNextEncoding, foobarString, 'utf16le', false, false, 'String', execa);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode first', testGeneratorNextEncoding, foobarString, 'utf16le', true, false, 'String', execa);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode both', testGeneratorNextEncoding, foobarString, 'utf16le', true, true, 'String', execa);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', false, false, 'String', execa);
test('Next generator argument is Uint8Array with encoding "utf16le",, with Uint8Array writes, objectMode first', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, false, 'Uint8Array', execa);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes, objectMode both', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, true, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with string writes', testGeneratorNextEncoding, foobarString, 'buffer', false, false, 'Uint8Array', execa);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode first', testGeneratorNextEncoding, foobarString, 'buffer', true, false, 'String', execa);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode both', testGeneratorNextEncoding, foobarString, 'buffer', true, true, 'String', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'buffer', false, false, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode first', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, false, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode both', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, true, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "hex", with string writes', testGeneratorNextEncoding, foobarString, 'hex', false, false, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "hex", with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'hex', false, false, 'Uint8Array', execa);
test('Next generator argument is object with default encoding, with object writes, objectMode first', testGeneratorNextEncoding, foobarObject, 'utf8', true, false, 'Object', execa);
test('Next generator argument is object with default encoding, with object writes, objectMode both', testGeneratorNextEncoding, foobarObject, 'utf8', true, true, 'Object', execa);
test('Next generator argument is string with default encoding, with string writes, sync', testGeneratorNextEncoding, foobarString, 'utf8', false, false, 'String', execaSync);
test('Next generator argument is string with default encoding, with string writes, objectMode first, sync', testGeneratorNextEncoding, foobarString, 'utf8', true, false, 'String', execaSync);
test('Next generator argument is string with default encoding, with string writes, objectMode both, sync', testGeneratorNextEncoding, foobarString, 'utf8', true, true, 'String', execaSync);
test('Next generator argument is string with default encoding, with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf8', false, false, 'String', execaSync);
test('Next generator argument is Uint8Array with default encoding, with Uint8Array writes, objectMode first, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, false, 'Uint8Array', execaSync);
test('Next generator argument is string with default encoding, with Uint8Array writes, objectMode both, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, true, 'Uint8Array', execaSync);
test('Next generator argument is string with encoding "utf16le", with string writes, sync', testGeneratorNextEncoding, foobarString, 'utf16le', false, false, 'String', execaSync);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode first, sync', testGeneratorNextEncoding, foobarString, 'utf16le', true, false, 'String', execaSync);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode both, sync', testGeneratorNextEncoding, foobarString, 'utf16le', true, true, 'String', execaSync);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', false, false, 'String', execaSync);
test('Next generator argument is Uint8Array with encoding "utf16le",, with Uint8Array writes, objectMode first, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, false, 'Uint8Array', execaSync);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes, objectMode both, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, true, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with string writes, sync', testGeneratorNextEncoding, foobarString, 'buffer', false, false, 'Uint8Array', execaSync);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode first, sync', testGeneratorNextEncoding, foobarString, 'buffer', true, false, 'String', execaSync);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode both, sync', testGeneratorNextEncoding, foobarString, 'buffer', true, true, 'String', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'buffer', false, false, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode first, sync', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, false, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode both, sync', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, true, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "hex", with string writes, sync', testGeneratorNextEncoding, foobarString, 'hex', false, false, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "hex", with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'hex', false, false, 'Uint8Array', execaSync);
test('Next generator argument is object with default encoding, with object writes, objectMode first, sync', testGeneratorNextEncoding, foobarObject, 'utf8', true, false, 'Object', execaSync);
test('Next generator argument is object with default encoding, with object writes, objectMode both, sync', testGeneratorNextEncoding, foobarObject, 'utf8', true, true, 'Object', execaSync);

const testFirstOutputGeneratorArgument = async (t, fdNumber, execaMethod) => {
	const lines = [];
	await execaMethod('noop-fd.js', [`${fdNumber}`], getStdio(fdNumber, getTypeofGenerator(lines)(true)));
	assertTypeofChunk(t, lines, 'String');
};

test('The first generator with result.stdout does not receive an object argument even in objectMode', testFirstOutputGeneratorArgument, 1, execa);
test('The first generator with result.stderr does not receive an object argument even in objectMode', testFirstOutputGeneratorArgument, 2, execa);
test('The first generator with result.stdio[*] does not receive an object argument even in objectMode', testFirstOutputGeneratorArgument, 3, execa);
test('The first generator with result.stdout does not receive an object argument even in objectMode, sync', testFirstOutputGeneratorArgument, 1, execaSync);
test('The first generator with result.stderr does not receive an object argument even in objectMode, sync', testFirstOutputGeneratorArgument, 2, execaSync);
test('The first generator with result.stdio[*] does not receive an object argument even in objectMode, sync', testFirstOutputGeneratorArgument, 3, execaSync);
