import { isPathUnderWorkspace } from "../gateway/workspace-editor-path.js";
import { createReadStream } from "node:fs";
import { lstat, readdir, realpath, stat } from "node:fs/promises";
import { relative, resolve } from "node:path";
import { Readable, Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
import { crc32, createDeflateRaw } from "node:zlib";
//#region src/share/share-zip.ts
const SIG_LOCAL = 67324752;
const SIG_CENTRAL = 33639248;
const SIG_EOCD = 101010256;
const SIG_EOCD64 = 101075792;
const SIG_EOCD64_LOCATOR = 117853008;
const SIG_DATA_DESCRIPTOR = 134695760;
const ZIP64_THRESHOLD = 4294967295;
const COUNT_ZIP64_THRESHOLD = 65535;
const FLAG_USE_DATA_DESCRIPTOR = 8;
const FLAG_UTF8 = 2048;
const METHOD_STORE = 0;
const METHOD_DEFLATE = 8;
function dosTime(d) {
	return d.getHours() << 11 | d.getMinutes() << 5 | Math.floor(d.getSeconds() / 2);
}
function dosDate(d) {
	return d.getFullYear() - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate();
}
function localFileHeader(name, mtimeDate, method) {
	const buf = Buffer.alloc(30 + name.length);
	buf.writeUInt32LE(SIG_LOCAL, 0);
	buf.writeUInt16LE(45, 4);
	buf.writeUInt16LE(FLAG_USE_DATA_DESCRIPTOR | FLAG_UTF8, 6);
	buf.writeUInt16LE(method, 8);
	buf.writeUInt16LE(dosTime(mtimeDate), 10);
	buf.writeUInt16LE(dosDate(mtimeDate), 12);
	buf.writeUInt32LE(0, 14);
	buf.writeUInt32LE(0, 18);
	buf.writeUInt32LE(0, 22);
	buf.writeUInt16LE(name.length, 26);
	buf.writeUInt16LE(0, 28);
	name.copy(buf, 30);
	return buf;
}
function dataDescriptor(crc, compressed, uncompressed) {
	if (compressed > ZIP64_THRESHOLD || uncompressed > ZIP64_THRESHOLD) {
		const buf = Buffer.alloc(24);
		buf.writeUInt32LE(SIG_DATA_DESCRIPTOR, 0);
		buf.writeUInt32LE(crc >>> 0, 4);
		buf.writeBigUInt64LE(BigInt(compressed), 8);
		buf.writeBigUInt64LE(BigInt(uncompressed), 16);
		return buf;
	}
	const buf = Buffer.alloc(16);
	buf.writeUInt32LE(SIG_DATA_DESCRIPTOR, 0);
	buf.writeUInt32LE(crc >>> 0, 4);
	buf.writeUInt32LE(compressed >>> 0, 8);
	buf.writeUInt32LE(uncompressed >>> 0, 12);
	return buf;
}
function buildZip64Extra(entry) {
	const fields = [];
	const needUncompressed = entry.uncompressedSize > ZIP64_THRESHOLD;
	const needCompressed = entry.compressedSize > ZIP64_THRESHOLD;
	const needOffset = entry.offset > ZIP64_THRESHOLD;
	if (needUncompressed) fields.push(BigInt(entry.uncompressedSize));
	if (needCompressed) fields.push(BigInt(entry.compressedSize));
	if (needOffset) fields.push(BigInt(entry.offset));
	const buf = Buffer.alloc(4 + fields.length * 8);
	buf.writeUInt16LE(1, 0);
	buf.writeUInt16LE(fields.length * 8, 2);
	for (let i = 0; i < fields.length; i++) buf.writeBigUInt64LE(fields[i], 4 + i * 8);
	return buf;
}
function centralHeader(entry) {
	const extra = entry.compressedSize > ZIP64_THRESHOLD || entry.uncompressedSize > ZIP64_THRESHOLD || entry.offset > ZIP64_THRESHOLD ? buildZip64Extra(entry) : Buffer.alloc(0);
	const date = new Date(entry.mtime);
	const buf = Buffer.alloc(46 + entry.name.length + extra.length);
	buf.writeUInt32LE(SIG_CENTRAL, 0);
	buf.writeUInt16LE(45, 4);
	buf.writeUInt16LE(45, 6);
	buf.writeUInt16LE(FLAG_USE_DATA_DESCRIPTOR | FLAG_UTF8, 8);
	buf.writeUInt16LE(entry.method, 10);
	buf.writeUInt16LE(dosTime(date), 12);
	buf.writeUInt16LE(dosDate(date), 14);
	buf.writeUInt32LE(entry.crc32 >>> 0, 16);
	buf.writeUInt32LE(Math.min(entry.compressedSize, 4294967295) >>> 0, 20);
	buf.writeUInt32LE(Math.min(entry.uncompressedSize, 4294967295) >>> 0, 24);
	buf.writeUInt16LE(entry.name.length, 28);
	buf.writeUInt16LE(extra.length, 30);
	buf.writeUInt16LE(0, 32);
	buf.writeUInt16LE(0, 34);
	buf.writeUInt16LE(0, 36);
	buf.writeUInt32LE(0, 38);
	buf.writeUInt32LE(Math.min(entry.offset, 4294967295) >>> 0, 42);
	entry.name.copy(buf, 46);
	extra.copy(buf, 46 + entry.name.length);
	return buf;
}
function endOfCentralDir(totalEntries, cdSize, cdOffset, needsZip64) {
	const parts = [];
	if (needsZip64) {
		const eocd64 = Buffer.alloc(56);
		eocd64.writeUInt32LE(SIG_EOCD64, 0);
		eocd64.writeBigUInt64LE(BigInt(44), 4);
		eocd64.writeUInt16LE(45, 12);
		eocd64.writeUInt16LE(45, 14);
		eocd64.writeUInt32LE(0, 16);
		eocd64.writeUInt32LE(0, 20);
		eocd64.writeBigUInt64LE(BigInt(totalEntries), 24);
		eocd64.writeBigUInt64LE(BigInt(totalEntries), 32);
		eocd64.writeBigUInt64LE(BigInt(cdSize), 40);
		eocd64.writeBigUInt64LE(BigInt(cdOffset), 48);
		parts.push(eocd64);
		const locator = Buffer.alloc(20);
		locator.writeUInt32LE(SIG_EOCD64_LOCATOR, 0);
		locator.writeUInt32LE(0, 4);
		locator.writeBigUInt64LE(BigInt(cdOffset + cdSize), 8);
		locator.writeUInt32LE(1, 16);
		parts.push(locator);
	}
	const eocd = Buffer.alloc(22);
	eocd.writeUInt32LE(SIG_EOCD, 0);
	eocd.writeUInt16LE(0, 4);
	eocd.writeUInt16LE(0, 6);
	eocd.writeUInt16LE(Math.min(totalEntries, 65535), 8);
	eocd.writeUInt16LE(Math.min(totalEntries, 65535), 10);
	eocd.writeUInt32LE(Math.min(cdSize, 4294967295) >>> 0, 12);
	eocd.writeUInt32LE(Math.min(cdOffset, 4294967295) >>> 0, 16);
	eocd.writeUInt16LE(0, 20);
	parts.push(eocd);
	return Buffer.concat(parts);
}
const NO_COMPRESS_EXT = new Set([
	"zip",
	"gz",
	"bz2",
	"xz",
	"rar",
	"7z",
	"png",
	"jpg",
	"jpeg",
	"gif",
	"webp",
	"avif",
	"heic",
	"mp3",
	"mp4",
	"mkv",
	"mov",
	"webm",
	"ogg",
	"opus",
	"pdf"
]);
function defaultShouldCompress(f) {
	const ext = f.zipPath.split(".").pop()?.toLowerCase() ?? "";
	if (NO_COMPRESS_EXT.has(ext)) return false;
	if (f.size > 32 * 1024 * 1024) return false;
	return true;
}
/**
* Build a streaming ZIP archive as a Node Readable. Files that fail to read
* are skipped and listed in a synthetic `_xopc_errors.txt` trailer entry.
*/
function createZipStream(opts) {
	const errors = [];
	const central = [];
	const compress = opts.compress ?? defaultShouldCompress;
	const stream = new Readable({ read() {} });
	let position = 0;
	function pushBuf(buf) {
		position += buf.length;
		stream.push(buf);
	}
	async function emitEntry(file) {
		const nameBuf = Buffer.from(file.zipPath, "utf8");
		const method = compress(file) ? METHOD_DEFLATE : METHOD_STORE;
		const offset = position;
		const mtime = file.mtimeMs > 0 ? file.mtimeMs : Date.now();
		pushBuf(localFileHeader(nameBuf, new Date(mtime), method));
		let uncompressed = 0;
		let compressed = 0;
		let crc = 0;
		const source = createReadStream(file.absolutePath);
		const counter = new Transform({ transform(chunk, _enc, cb) {
			const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
			uncompressed += buf.length;
			crc = crc32(buf, crc);
			cb(null, buf);
		} });
		const sink = new Transform({ transform(chunk, _enc, cb) {
			const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
			compressed += buf.length;
			pushBuf(buf);
			cb();
		} });
		if (method === METHOD_DEFLATE) await pipeline(source, counter, createDeflateRaw({ level: 6 }), sink);
		else await pipeline(source, counter, sink);
		pushBuf(dataDescriptor(crc, compressed, uncompressed));
		central.push({
			name: nameBuf,
			mtime,
			crc32: crc,
			compressedSize: compressed,
			uncompressedSize: uncompressed,
			offset,
			method
		});
	}
	async function emitSyntheticTextEntry(zipPath, content) {
		const nameBuf = Buffer.from(zipPath, "utf8");
		const offset = position;
		const mtime = Date.now();
		pushBuf(localFileHeader(nameBuf, new Date(mtime), METHOD_STORE));
		const buf = Buffer.from(content, "utf8");
		const crc = crc32(buf, 0);
		pushBuf(buf);
		pushBuf(dataDescriptor(crc, buf.length, buf.length));
		central.push({
			name: nameBuf,
			mtime,
			crc32: crc,
			compressedSize: buf.length,
			uncompressedSize: buf.length,
			offset,
			method: METHOD_STORE
		});
	}
	(async () => {
		try {
			for (const file of opts.files) try {
				await emitEntry(file);
			} catch (err) {
				const msg = err instanceof Error ? err.message : String(err);
				errors.push(`${file.zipPath}: ${msg}`);
			}
			if (errors.length > 0) await emitSyntheticTextEntry("_xopc_errors.txt", errors.join("\n"));
			const cdOffset = position;
			let needsZip64 = central.length > COUNT_ZIP64_THRESHOLD;
			for (const entry of central) {
				if (entry.compressedSize > ZIP64_THRESHOLD || entry.uncompressedSize > ZIP64_THRESHOLD || entry.offset > ZIP64_THRESHOLD) needsZip64 = true;
				pushBuf(centralHeader(entry));
			}
			const cdSize = position - cdOffset;
			pushBuf(endOfCentralDir(central.length, cdSize, cdOffset, needsZip64));
			stream.push(null);
		} catch (err) {
			stream.destroy(err instanceof Error ? err : new Error(String(err)));
		}
	})();
	return stream;
}
async function planDirectoryFiles(record, opts) {
	if (record.kind !== "directory") return [];
	const rootAbs = resolve(record.absolutePath, opts.rootRelativePath);
	const files = [];
	let totalSize = 0;
	async function walk(dir, depth) {
		if (depth > opts.maxDepth) return;
		if (files.length >= opts.maxFileCount) return;
		const dirents = await readdir(dir, { withFileTypes: true });
		for (const dirent of dirents) {
			if (files.length >= opts.maxFileCount) return;
			const abs = resolve(dir, dirent.name);
			const childLstat = await lstat(abs);
			let stats = childLstat;
			if (childLstat.isSymbolicLink()) {
				if (!opts.followSymlinks) continue;
				const real = await realpath(abs);
				if (!isPathUnderWorkspace(record.workspaceRoot, real)) continue;
				if (relative(record.absolutePath, real).startsWith("..")) continue;
				stats = await stat(abs);
			}
			if (stats.isFile()) {
				totalSize += stats.size;
				if (totalSize > opts.maxFolderSize) throw new Error("zip exceeds maxFolderSize");
				const zipRel = relative(rootAbs, abs).split(/[\\/]/).join("/");
				files.push({
					zipPath: zipRel,
					absolutePath: abs,
					mtimeMs: stats.mtimeMs,
					size: stats.size
				});
			} else if (stats.isDirectory()) await walk(abs, depth + 1);
		}
	}
	const rootLstat = await lstat(rootAbs);
	if (rootLstat.isDirectory()) await walk(rootAbs, 0);
	else if (rootLstat.isFile()) {
		const rootStat = await stat(rootAbs);
		files.push({
			zipPath: relative(record.absolutePath, rootAbs).split(/[\\/]/).join("/"),
			absolutePath: rootAbs,
			mtimeMs: rootStat.mtimeMs,
			size: rootStat.size
		});
	}
	return files;
}
//#endregion
export { createZipStream, planDirectoryFiles };

//# sourceMappingURL=share-zip.js.map