import { spawn } from 'child_process';
import path from 'path';
import fs from 'fs/promises';
import { createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
import logger from './logger.js';
import { directoryContainsCorruptedNames } from './filenameEncoding.js';
const ZIP_FILENAME_ENCODINGS = ['utf-8', 'gbk'];
async function copyDirectoryContents(sourceDir, targetDir) {
    await fs.mkdir(targetDir, { recursive: true });
    const entries = await fs.readdir(sourceDir, { withFileTypes: true });
    for (const entry of entries) {
        const sourcePath = path.join(sourceDir, entry.name);
        const targetPath = path.join(targetDir, entry.name);
        if (entry.isDirectory()) {
            await copyDirectoryContents(sourcePath, targetPath);
            continue;
        }
        await fs.copyFile(sourcePath, targetPath);
    }
}
async function moveExtractedEntry(sourcePath, targetPath, isDirectory) {
    try {
        await fs.rename(sourcePath, targetPath);
        return;
    }
    catch (error) {
        if (error?.code === 'EXDEV') {
            if (isDirectory) {
                await copyDirectoryContents(sourcePath, targetPath);
                await fs.rm(sourcePath, { recursive: true, force: true });
            }
            else {
                await fs.copyFile(sourcePath, targetPath);
                await fs.rm(sourcePath, { force: true });
            }
            return;
        }
        if (isDirectory && ['EEXIST', 'ENOTEMPTY', 'EPERM'].includes(error?.code)) {
            await moveDirectoryContents(sourcePath, targetPath);
            await fs.rm(sourcePath, { recursive: true, force: true });
            return;
        }
        if (!isDirectory && ['EEXIST', 'EPERM'].includes(error?.code)) {
            await fs.rm(targetPath, { force: true });
            await fs.rename(sourcePath, targetPath);
            return;
        }
        throw error;
    }
}
async function moveDirectoryContents(sourceDir, targetDir) {
    await fs.mkdir(targetDir, { recursive: true });
    const entries = await fs.readdir(sourceDir, { withFileTypes: true });
    for (const entry of entries) {
        const sourcePath = path.join(sourceDir, entry.name);
        const targetPath = path.join(targetDir, entry.name);
        await moveExtractedEntry(sourcePath, targetPath, entry.isDirectory());
    }
}
async function createZipExtractTempDir(targetDir) {
    return fs.mkdtemp(path.join(path.dirname(targetDir), '.gsm3-zip-extract-'));
}
const SUPPORTED_PLATFORMS = new Set(['win32', 'linux', 'darwin']);
const SUPPORTED_ARCHS = new Set(['x64', 'arm64']);
class ZipToolsManager {
    constructor() {
        this.DOWNLOAD_URL = 'https://github.com/MCSManager/Zip-Tools/releases/latest/download/';
    }
    getBinaryName() {
        const platform = process.platform;
        const arch = process.arch;
        if (!SUPPORTED_PLATFORMS.has(platform)) {
            throw new Error(`不支持的操作系统平台: ${platform}`);
        }
        if (!SUPPORTED_ARCHS.has(arch)) {
            throw new Error(`不支持的 CPU 架构: ${arch}`);
        }
        const archLabel = (platform === 'darwin' && arch === 'x64') ? 'amd64' : arch;
        const name = `file_zip_${platform}_${archLabel}`;
        return platform === 'win32' ? `${name}.exe` : name;
    }
    getLibDirCandidates() {
        const baseDir = process.cwd();
        return [
            path.join(baseDir, 'data', 'lib'),
            path.join(baseDir, 'server', 'data', 'lib'),
        ];
    }
    async getZipToolsPath() {
        const binaryName = this.getBinaryName();
        const candidates = this.getLibDirCandidates();
        for (const libDir of candidates) {
            const fullPath = path.join(libDir, binaryName);
            try {
                await fs.access(fullPath);
                return fullPath;
            }
            catch {
            }
        }
        throw new Error(`未找到 Zip-Tools 二进制文件 (${binaryName})，已尝试路径: ${candidates.map(d => path.join(d, binaryName)).join(', ')}`);
    }
    async isInstalled() {
        try {
            await this.getZipToolsPath();
            return true;
        }
        catch {
            return false;
        }
    }
    async downloadFromUrl(url, targetPath) {
        const axios = (await import('axios')).default;
        const response = await axios.get(url, {
            responseType: 'stream',
            timeout: 60000,
        });
        const writer = createWriteStream(targetPath);
        await pipeline(response.data, writer);
        const stat = await fs.stat(targetPath);
        if (stat.size === 0) {
            await fs.unlink(targetPath);
            throw new Error('下载的文件大小为 0，已删除');
        }
        if (process.platform !== 'win32') {
            await fs.chmod(targetPath, 0o755);
        }
    }
    async download() {
        const binaryName = this.getBinaryName();
        const candidates = this.getLibDirCandidates();
        let targetDir = null;
        for (const dir of candidates) {
            try {
                await fs.mkdir(dir, { recursive: true });
                targetDir = dir;
                break;
            }
            catch {
            }
        }
        if (!targetDir) {
            throw new Error(`无法创建 lib 目录，已尝试: ${candidates.join(', ')}`);
        }
        const targetPath = path.join(targetDir, binaryName);
        const downloadUrl = `${this.DOWNLOAD_URL}${binaryName}`;
        logger.info(`正在从 GitHub 下载 Zip-Tools: ${downloadUrl}`);
        try {
            await this.downloadFromUrl(downloadUrl, targetPath);
            logger.info(`Zip-Tools 下载完成: ${targetPath}`);
        }
        catch (error) {
            try {
                await fs.unlink(targetPath);
            }
            catch { }
            const message = `Zip-Tools 下载失败（GitHub）: ${error.message || error}`;
            logger.error(message);
            throw new Error(message);
        }
    }
    async ensureInstalled() {
        if (await this.isInstalled()) {
            logger.info('Zip-Tools 已存在，跳过下载');
            return;
        }
        await this.download();
    }
    get7zBinaryName() {
        const platform = process.platform;
        const arch = process.arch;
        if (!SUPPORTED_PLATFORMS.has(platform)) {
            throw new Error(`不支持的操作系统平台: ${platform}`);
        }
        if (!SUPPORTED_ARCHS.has(arch)) {
            throw new Error(`不支持的 CPU 架构: ${arch}`);
        }
        const name = `7z_${platform}_${arch}`;
        return platform === 'win32' ? `${name}.exe` : name;
    }
    async get7zPath() {
        const binaryName = this.get7zBinaryName();
        const candidates = this.getLibDirCandidates();
        for (const libDir of candidates) {
            const fullPath = path.join(libDir, binaryName);
            try {
                await fs.access(fullPath);
                return fullPath;
            }
            catch {
            }
        }
        throw new Error(`未找到 7z 二进制文件 (${binaryName})，已尝试路径: ${candidates.map(d => path.join(d, binaryName)).join(', ')}`);
    }
    async is7zInstalled() {
        try {
            await this.get7zPath();
            return true;
        }
        catch {
            return false;
        }
    }
    async download7z() {
        const binaryName = this.get7zBinaryName();
        const candidates = this.getLibDirCandidates();
        let targetDir = null;
        for (const dir of candidates) {
            try {
                await fs.mkdir(dir, { recursive: true });
                targetDir = dir;
                break;
            }
            catch {
            }
        }
        if (!targetDir) {
            throw new Error(`无法创建 lib 目录，已尝试: ${candidates.join(', ')}`);
        }
        const targetPath = path.join(targetDir, binaryName);
        const downloadUrl = `${this.DOWNLOAD_URL}${binaryName}`;
        logger.info(`正在从 GitHub 下载 7z: ${downloadUrl}`);
        try {
            await this.downloadFromUrl(downloadUrl, targetPath);
            logger.info(`7z 下载完成: ${targetPath}`);
        }
        catch (error) {
            try {
                await fs.unlink(targetPath);
            }
            catch { }
            const message = `7z 下载失败（GitHub）: ${error.message || error}`;
            logger.error(message);
            throw new Error(message);
        }
    }
    async ensure7zInstalled() {
        if (await this.is7zInstalled()) {
            logger.info('7z 已存在，跳过下载');
            return;
        }
        await this.download7z();
    }
    buildProcessStartError(toolName, toolPath, error) {
        const resolvedToolPath = path.resolve(toolPath);
        if (error.code === 'EACCES') {
            return new Error(`${toolName} 无法启动：压缩工具缺少执行权限，请为该文件添加可执行权限后重试。参考命令: chmod +x "${resolvedToolPath}"。工具路径: ${resolvedToolPath}`);
        }
        if (error.code === 'ENOENT') {
            return new Error(`${toolName} 无法启动：未找到压缩工具文件，请检查文件是否存在或重新下载依赖。工具路径: ${resolvedToolPath}`);
        }
        return new Error(`${toolName} 进程启动失败: ${error.message || '未知错误'}。工具路径: ${resolvedToolPath}`);
    }
    execute7z(toolPath, args, cwd) {
        return new Promise((resolve, reject) => {
            const child = spawn(toolPath, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
            let stderr = '';
            child.stderr.on('data', (data) => {
                stderr += data.toString();
            });
            child.on('error', (error) => {
                reject(this.buildProcessStartError('7z', toolPath, error));
            });
            child.on('close', (code) => {
                if (code === 0) {
                    resolve();
                }
                else {
                    reject(new Error(`7z 执行失败 (退出码: ${code}): ${stderr.trim() || '未知错误'}`));
                }
            });
        });
    }
    async extract7z(archivePath, targetDir) {
        await this.ensure7zInstalled();
        const toolPath = await this.get7zPath();
        await fs.mkdir(targetDir, { recursive: true });
        const args = ['x', archivePath, `-o${path.resolve(targetDir)}`];
        await this.execute7z(toolPath, args);
    }
    async compress7z(archivePath, files, cwd) {
        await this.ensure7zInstalled();
        const toolPath = await this.get7zPath();
        const args = ['a', archivePath, ...files];
        await this.execute7z(toolPath, args, cwd);
    }
    async extractZip(zipPath, targetDir) {
        const toolPath = await this.getZipToolsPath();
        const zipDir = path.dirname(zipPath);
        const zipFileName = path.basename(zipPath);
        await fs.mkdir(targetDir, { recursive: true });
        let chosenTempDir = '';
        let sawCorruptedNames = false;
        let fallbackError = null;
        for (let index = 0; index < ZIP_FILENAME_ENCODINGS.length; index++) {
            const encoding = ZIP_FILENAME_ENCODINGS[index];
            const tempTargetDir = await createZipExtractTempDir(targetDir);
            const args = [
                '-mode', '2',
                '-zipPath', zipFileName,
                '-distDirPath', path.resolve(tempTargetDir),
                '-code', encoding,
            ];
            try {
                await this.executeZipTools(toolPath, args, zipDir);
            }
            catch (error) {
                await fs.rm(tempTargetDir, { recursive: true, force: true });
                fallbackError = error instanceof Error ? error : new Error(String(error));
                logger.warn(`ZIP 解压尝试失败 (${encoding}): ${fallbackError.message}`);
                continue;
            }
            const hasCorruptedNames = await directoryContainsCorruptedNames(tempTargetDir);
            if (!hasCorruptedNames) {
                chosenTempDir = tempTargetDir;
                break;
            }
            sawCorruptedNames = true;
            logger.warn(`ZIP 解压检测到损坏文件名，准备使用下一种编码重试: ${zipPath} (${encoding})`);
            if (index === ZIP_FILENAME_ENCODINGS.length - 1) {
                chosenTempDir = tempTargetDir;
            }
            else {
                await fs.rm(tempTargetDir, { recursive: true, force: true });
            }
        }
        if (!chosenTempDir) {
            throw fallbackError ?? new Error(`ZIP 解压失败: ${zipPath}`);
        }
        await moveDirectoryContents(chosenTempDir, targetDir);
        await fs.rm(chosenTempDir, { recursive: true, force: true });
        if (sawCorruptedNames) {
            logger.warn(`ZIP 文件 ${zipPath} 在 UTF-8 解压下出现损坏文件名，已尝试使用 GBK 回退`);
        }
    }
    async compressZip(zipPath, files, cwd) {
        const toolPath = await this.getZipToolsPath();
        const zipFileName = path.basename(zipPath);
        const args = [
            '-mode', '1',
            ...files.flatMap(f => ['-file', f]),
            '-zipPath', zipFileName,
            '-code', 'utf-8',
        ];
        await this.executeZipTools(toolPath, args, cwd);
    }
    executeZipTools(toolPath, args, cwd) {
        return new Promise((resolve, reject) => {
            const child = spawn(toolPath, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
            let stderr = '';
            child.stderr.on('data', (data) => {
                stderr += data.toString();
            });
            child.on('error', (error) => {
                reject(this.buildProcessStartError('Zip-Tools', toolPath, error));
            });
            child.on('close', (code) => {
                if (code === 0) {
                    resolve();
                }
                else {
                    reject(new Error(`Zip-Tools 执行失败 (退出码: ${code}): ${stderr.trim() || '未知错误'}`));
                }
            });
        });
    }
}
export const zipToolsManager = new ZipToolsManager();
export { ZipToolsManager };
