'use strict';
const path = require('path');
const BufferConstants = require('buffer').constants;
const Vinyl = require('vinyl');
const PluginError = require('plugin-error');
const through = require('through2');
const Yazl = require('yazl');
const getStream = require('get-stream');

module.exports = (filename, options) => {
	if (!filename) {
		throw new PluginError('gulp-zip', '`filename` required');
	}

	options = {
		compress: true,
		buffer: true,
		...options
	};

	let firstFile;
	const zip = new Yazl.ZipFile();

	return through.obj((file, encoding, callback) => {
		if (!firstFile) {
			firstFile = file;
		}

		// Because Windows...
		const pathname = file.relative.replace(/\\/g, '/');

		if (!pathname) {
			callback();
			return;
		}

		if (file.isNull() && file.stat && file.stat.isDirectory && file.stat.isDirectory()) {
			zip.addEmptyDirectory(pathname, {
				mtime: options.modifiedTime || file.stat.mtime || new Date()
				// Do *not* pass a mode for a directory, because it creates platform-dependent
				// ZIP files (ZIP files created on Windows that cannot be opened on macOS).
				// Re-enable if this PR is resolved: https://github.com/thejoshwolfe/yazl/pull/59
				// mode: file.stat.mode
			});
		} else {
			const stat = {
				compress: options.compress,
				mtime: options.modifiedTime || (file.stat ? file.stat.mtime : new Date()),
				mode: file.stat ? file.stat.mode : null
			};

			if (file.isStream()) {
				zip.addReadStream(file.contents, pathname, stat);
			}

			if (file.isBuffer()) {
				zip.addBuffer(file.contents, pathname, stat);
			}
		}

		callback();
	}, function (callback) {
		if (!firstFile) {
			callback();
			return;
		}

		(async () => {
			let data;
			if (options.buffer) {
				try {
					data = await getStream.buffer(zip.outputStream, {maxBuffer: BufferConstants.MAX_LENGTH});
				} catch (error) {
					if (error instanceof getStream.MaxBufferError) {
						callback(new PluginError('gulp-zip', 'The output ZIP file is too big to store in a buffer (larger than Buffer MAX_LENGTH). To output a stream instead, set the gulp-zip buffer option to `false`.'));
					} else {
						callback(error);
					}

					return;
				}
			} else {
				data = zip.outputStream;
			}

			this.push(new Vinyl({
				cwd: firstFile.cwd,
				base: firstFile.base,
				path: path.join(firstFile.base, filename),
				contents: data
			}));

			callback();
		})();

		zip.end();
	});
};