import util from "node:util";
import cp$1 from "node:child_process";
import path from "node:path";
import fs from "node:fs/promises";
import zlib from "node:zlib";
import os from "node:os";

//#region ../../node_modules/.pnpm/tsdown@0.20.3_@arethetypeswrong+core@0.18.2_@typescript+native-preview@7.0.0-dev.202601_0f996e6618fcfeebf135203f4e3013f2/node_modules/tsdown/dist/index-node-BjDys8jy.mjs
/** @type {import('../index.d.ts').pack} */
async function pack(dir, opts) {
	const packageManager = opts?.packageManager ?? "npm";
	let command = `${packageManager} pack`;
	if (packageManager === "bun") command = command.replace("bun", "bun pm");
	const packDestination = opts?.destination ?? dir;
	if (opts?.destination) switch (packageManager) {
		case "yarn":
			command += ` --out \"${path.join(packDestination, "package.tgz")}\"`;
			break;
		case "bun":
			command += ` --destination \"${packDestination}\"`;
			break;
		default:
			command += ` --pack-destination \"${packDestination}\"`;
			break;
	}
	if (opts?.ignoreScripts) switch (packageManager) {
		case "pnpm":
			command += " --config.ignore-scripts=true";
			break;
		case "yarn": break;
		default:
			command += " --ignore-scripts";
			break;
	}
	const output = await util.promisify(cp$1.exec)(command, { cwd: dir });
	const tarballFile = await fs.readdir(packDestination).then((files) => {
		return files.find((file) => file.endsWith(".tgz") && output.stdout.includes(file));
	});
	if (!tarballFile) if (output.stdout.startsWith("yarn pack v1")) throw new Error(`Yarn 1 is not supported to pack files. Command output:\n${JSON.stringify(output, null, 2)}`);
	else throw new Error(`Failed to find packed tarball file in ${packDestination}. Command output:\n${JSON.stringify(output, null, 2)}`);
	return path.join(packDestination, tarballFile);
}
async function getTempPackDir() {
	const tempDir = os.tmpdir() + path.sep;
	const tempPackDir = await fs.mkdtemp(tempDir + "publint-pack-");
	return await fs.realpath(tempPackDir);
}
/** @type {import('../index.d.ts').packAsJson} */
async function packAsJson(dir, opts) {
	const packageManager = opts?.packageManager ?? "npm";
	if (packageManager === "bun") throw new Error("`packAsJson` is not supported for `bun`");
	let command = `${packageManager} pack --json`;
	const supportsDryRun = packageManager === "npm" || packageManager === "yarn";
	/** @type {string | undefined} */
	let packDestination;
	if (supportsDryRun) command += " --dry-run";
	else {
		packDestination = await getTempPackDir();
		command += ` --pack-destination ${packDestination}`;
	}
	if (opts?.ignoreScripts) switch (packageManager) {
		case "pnpm":
			command += " --config.ignore-scripts=true";
			break;
		case "yarn": break;
		default:
			command += " --ignore-scripts";
			break;
	}
	let { stdout } = await util.promisify(cp$1.exec)(command, { cwd: dir });
	try {
		stdout = stdout.trim();
		if (packageManager === "pnpm") stdout = fixPnpmStdout(stdout);
		else if (packageManager === "yarn") stdout = fixYarnStdout(stdout);
		return JSON.parse(stdout);
	} finally {
		if (!supportsDryRun && packDestination) await fs.rm(packDestination, { recursive: true });
	}
}
/**
* @param {string} stdout
*/
function fixPnpmStdout(stdout) {
	if (stdout.startsWith("{")) return stdout;
	const usualStartIndex = /\{\s*"name"/.exec(stdout)?.index;
	if (usualStartIndex != null) return stdout.slice(usualStartIndex);
	const firstBraceIndex = stdout.indexOf("{");
	if (firstBraceIndex !== -1) return stdout.slice(firstBraceIndex);
	return stdout;
}
/**
* @param {string} stdout
*/
function fixYarnStdout(stdout) {
	const lines = stdout.split("\n");
	let fixedStdout = "[";
	for (const line of lines) if (line) fixedStdout += line + ",";
	if (fixedStdout[fixedStdout.length - 1] === ",") fixedStdout = fixedStdout.slice(0, -1);
	fixedStdout += "]";
	return fixedStdout;
}
/**
* @param {ReadableStream<Uint8Array>} readableStream
* @returns {Promise<ArrayBuffer>}
*/
async function readableStreamToArrayBuffer(readableStream) {
	return await new Response(readableStream).arrayBuffer();
}
/**
* @param {ArrayBuffer} buffer
* @returns {import('../index.d.ts').TarballFile[]}
*/
function parseTar(buffer) {
	const decoder = new TextDecoder();
	/** @type {import('../index.d.ts').TarballFile[]} */
	const files = [];
	let offset = 0;
	while (offset < buffer.byteLength) {
		const type = read(buffer, decoder, offset + 156, 1);
		if (type === "\0") break;
		const size = parseInt(read(buffer, decoder, offset + 124, 12), 8);
		if (type === "0") {
			const name = read(buffer, decoder, offset, 100).split("\0", 1)[0];
			const data = new Uint8Array(buffer, offset + 512, size);
			files.push({
				name,
				data
			});
		}
		offset += 512 + Math.ceil(size / 512) * 512;
	}
	return files;
}
/**
* @param {ArrayBuffer} buffer
* @param {TextDecoder} decoder
* @param {number} offset
* @param {number} length
*/
function read(buffer, decoder, offset, length) {
	const view = new Uint8Array(buffer, offset, length);
	return decoder.decode(view);
}
/**
* @param {import('../index.d.ts').TarballFile[]} files
*/
function getFilesRootDir(files) {
	return files.length ? files[0].name.split("/")[0] : "package";
}
/** @type {import('../index.d.ts').unpack} */
async function unpack(tarball) {
	/** @type {ArrayBuffer} */
	let buffer;
	if (tarball instanceof ReadableStream) buffer = await readableStreamToArrayBuffer(tarball.pipeThrough(new DecompressionStream("gzip")));
	else {
		const nodeBuffer = await util.promisify(zlib.gunzip)(tarball);
		buffer = nodeBuffer.buffer.slice(nodeBuffer.byteOffset, nodeBuffer.byteOffset + nodeBuffer.byteLength);
	}
	const files = parseTar(buffer);
	return {
		files,
		rootDir: getFilesRootDir(files)
	};
}
/** @type {import('../index.d.ts').packAsList} */
async function packAsList(dir, opts) {
	const packageManager = opts?.packageManager ?? "npm";
	try {
		return await packAsListWithJson(dir, packageManager, opts?.ignoreScripts);
	} catch {
		return await packAsListWithPack(dir, packageManager, opts?.ignoreScripts);
	}
}
/**
* NOTE: only exported for tests
* @internal
* @param {string} dir
* @param {NonNullable<import('../index.d.ts').PackAsListOptions['packageManager']>} packageManager
* @param {import('../index.d.ts').PackAsListOptions['ignoreScripts']} ignoreScripts
* @returns {Promise<string[]>}
*/
async function packAsListWithJson(dir, packageManager, ignoreScripts) {
	const stdoutJson = await packAsJson(dir, {
		packageManager,
		ignoreScripts
	});
	switch (packageManager) {
		case "npm": return parseNpmPackJson(stdoutJson);
		case "yarn": return parseYarnPackJson(stdoutJson);
		case "pnpm": return parsePnpmPackJson(stdoutJson);
		default: return [];
	}
}
/**
* NOTE: only exported for tests
* @internal
* @param {string} dir
* @param {NonNullable<import('../index.d.ts').PackAsListOptions['packageManager']>} packageManager
* @param {import('../index.d.ts').PackAsListOptions['ignoreScripts']} ignoreScripts
* @returns {Promise<string[]>}
*/
async function packAsListWithPack(dir, packageManager, ignoreScripts) {
	const destination = await getTempPackDir();
	const tarballPath = await pack(dir, {
		packageManager,
		ignoreScripts,
		destination
	});
	try {
		const nodeBuffer = await fs.readFile(tarballPath);
		const { files, rootDir } = await unpack(nodeBuffer.buffer.slice(nodeBuffer.byteOffset, nodeBuffer.byteOffset + nodeBuffer.byteLength));
		return files.map((file) => file.name.slice(rootDir.length + 1));
	} finally {
		await fs.rm(destination, { recursive: true });
	}
}
/**
* @param {any} stdoutJson
* @returns {string[]}
*/
function parseNpmPackJson(stdoutJson) {
	return stdoutJson[0].files.map((file) => file.path);
}
/**
* @param {any} stdoutJson
* @returns {string[]}
*/
function parseYarnPackJson(stdoutJson) {
	const files = [];
	for (const value of stdoutJson) if (value.location) files.push(value.location);
	return files;
}
/**
* @param {any} stdoutJson
* @returns {string[]}
*/
function parsePnpmPackJson(stdoutJson) {
	return stdoutJson.files.map((file) => file.path);
}
/** @type {import('../index.d.ts').getPackDirectory} */
async function getPackDirectory(dir, packageManager) {
	if (packageManager === "pnpm") try {
		const pkgJsonPath = path.resolve(dir, "package.json");
		const pkgJsonData = JSON.parse(await fs.readFile(pkgJsonPath, "utf-8"));
		if (pkgJsonData.publishConfig?.directory) return path.resolve(dir, pkgJsonData.publishConfig.directory);
	} catch {}
	return dir;
}

//#endregion
export { getPackDirectory, pack, packAsJson, packAsList, unpack };