// ABOUTME: Downloads and prepares Node.js and Git binaries for macOS embedded runtime.
// ABOUTME: Called during the build process to bundle these tools with the Tauri app bundle.

import * as fs from 'fs';
import * as path from 'path';
import * as https from 'https';
import { pipeline } from 'stream/promises';
import { execSync } from 'child_process';

// Version configuration - update these for new releases
const NODE_VERSION = '22.12.0';
const GIT_VERSION = '2.47.1';

// Node.js download URLs for macOS
const NODE_DOWNLOADS: Record<string, { url: string }> = {
	'x64': {
		url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-x64.tar.gz`
	},
	'arm64': {
		url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-arm64.tar.gz`
	}
};

interface DownloadOptions {
	arch: 'x64' | 'arm64';
	outputDir: string;
}

async function downloadFile(url: string, dest: string): Promise<void> {
	console.log(`Downloading ${url}...`);

	return new Promise((resolve, reject) => {
		const file = fs.createWriteStream(dest);
		const request = https.get(url, (response) => {
			if (response.statusCode === 302 || response.statusCode === 301) {
				// Handle redirect
				file.close();
				fs.unlinkSync(dest);
				downloadFile(response.headers.location!, dest).then(resolve).catch(reject);
				return;
			}

			if (response.statusCode !== 200) {
				reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
				return;
			}

			pipeline(response, file)
				.then(() => resolve())
				.catch(reject);
		});

		request.on('error', (err) => {
			fs.unlinkSync(dest);
			reject(err);
		});
	});
}

async function extractTarGz(archivePath: string, destDir: string): Promise<void> {
	console.log(`Extracting ${archivePath} to ${destDir}...`);
	execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, { stdio: 'inherit' });
}

async function prepareNodejs(options: DownloadOptions): Promise<string> {
	const { arch, outputDir } = options;
	const nodeConfig = NODE_DOWNLOADS[arch];
	const nodeDir = path.join(outputDir, 'node');

	if (fs.existsSync(nodeDir)) {
		console.log(`Node.js directory already exists at ${nodeDir}, skipping...`);
		return nodeDir;
	}

	fs.mkdirSync(nodeDir, { recursive: true });

	const tarPath = path.join(outputDir, `node-${arch}.tar.gz`);
	await downloadFile(nodeConfig.url, tarPath);

	// Extract to temp directory
	const tempDir = path.join(outputDir, 'node-temp');
	fs.mkdirSync(tempDir, { recursive: true });
	await extractTarGz(tarPath, tempDir);

	// Move contents from extracted folder to nodeDir
	const extractedFolder = fs.readdirSync(tempDir).find(f => f.startsWith('node-'));
	if (extractedFolder) {
		const srcDir = path.join(tempDir, extractedFolder);
		const files = fs.readdirSync(srcDir);
		for (const file of files) {
			fs.renameSync(path.join(srcDir, file), path.join(nodeDir, file));
		}
	}

	// Cleanup
	fs.rmSync(tempDir, { recursive: true, force: true });
	fs.unlinkSync(tarPath);

	// Replace bin/npm and bin/npx symlinks with shell wrappers.
	// Tauri resolves symlinks into regular files when bundling the .app, which
	// breaks `require('../lib/cli.js')` because the path resolves relative to
	// bin/ rather than lib/node_modules/npm/bin/ where the symlink target lives.
	// A shell wrapper computes its own directory at runtime and stays correct
	// whether or not Tauri has dereferenced it.
	const npmWrapper = `#!/bin/sh\nexec "$(dirname "$0")/node" "$(dirname "$0")/../lib/node_modules/npm/bin/npm-cli.js" "$@"\n`;
	const npxWrapper = `#!/bin/sh\nexec "$(dirname "$0")/node" "$(dirname "$0")/../lib/node_modules/npm/bin/npx-cli.js" "$@"\n`;
	const corepackWrapper = `#!/bin/sh\nexec "$(dirname "$0")/node" "$(dirname "$0")/../lib/node_modules/corepack/dist/corepack.js" "$@"\n`;
	fs.writeFileSync(path.join(nodeDir, 'bin', 'npm'), npmWrapper, { mode: 0o755 });
	fs.writeFileSync(path.join(nodeDir, 'bin', 'npx'), npxWrapper, { mode: 0o755 });
	fs.writeFileSync(path.join(nodeDir, 'bin', 'corepack'), corepackWrapper, { mode: 0o755 });
	console.log('Replaced bin/npm, bin/npx, and bin/corepack with Tauri-safe shell wrappers.');

	console.log(`Node.js prepared at ${nodeDir}`);
	return nodeDir;
}

async function prepareGit(options: DownloadOptions): Promise<string> {
	const { outputDir } = options;
	const gitDir = path.join(outputDir, 'git');

	if (fs.existsSync(gitDir)) {
		console.log(`Git directory already exists at ${gitDir}, skipping...`);
		return gitDir;
	}

	// On macOS, we create a wrapper that uses system git
	// Building git from source requires additional dependencies (Xcode CLT)
	// Most macOS systems have git available via Xcode or Homebrew

	fs.mkdirSync(path.join(gitDir, 'bin'), { recursive: true });

	// Create a wrapper script that falls back to system git
	const gitWrapper = `#!/bin/bash
# ABOUTME: Wrapper for git - uses system git if available
# ABOUTME: Ensures git works even if not separately bundled

if command -v /usr/bin/git &> /dev/null; then
	exec /usr/bin/git "$@"
elif command -v /opt/homebrew/bin/git &> /dev/null; then
	exec /opt/homebrew/bin/git "$@"
elif command -v /usr/local/bin/git &> /dev/null; then
	exec /usr/local/bin/git "$@"
else
	echo "Error: Git not found. Please install git via Xcode Command Line Tools or Homebrew." >&2
	exit 1
fi
`;

	fs.writeFileSync(path.join(gitDir, 'bin', 'git'), gitWrapper, { mode: 0o755 });

	console.log(`Git wrapper prepared at ${gitDir}`);
	return gitDir;
}

export async function prepareEmbeddedRuntime(arch: 'x64' | 'arm64', outputDir: string): Promise<{ nodeDir: string; gitDir: string }> {
	console.log(`Preparing embedded runtime for macOS ${arch}...`);

	fs.mkdirSync(outputDir, { recursive: true });

	const [nodeDir, gitDir] = await Promise.all([
		prepareNodejs({ arch, outputDir }),
		prepareGit({ arch, outputDir })
	]);

	// Create a version file for tracking
	const versionInfo = {
		node: NODE_VERSION,
		git: GIT_VERSION,
		arch,
		platform: 'darwin',
		preparedAt: new Date().toISOString()
	};
	fs.writeFileSync(
		path.join(outputDir, 'embedded-runtime.json'),
		JSON.stringify(versionInfo, null, 2)
	);

	console.log('Embedded runtime prepared successfully.');
	return { nodeDir, gitDir };
}

// CLI entry point (ESM compatible)
const arch = (process.argv[2] as 'x64' | 'arm64') || 'arm64';
const outputDir = process.argv[3] || path.join(process.cwd(), 'src-tauri', 'embedded-runtime', `darwin-${arch}`);

prepareEmbeddedRuntime(arch, outputDir)
	.then(() => process.exit(0))
	.catch((err) => {
		console.error('Failed to prepare embedded runtime:', err);
		process.exit(1);
	});
