/**
 * Archive Agent
 *
 * Specialized agent for managing archive files.
 * Supports zip, tar, tar.gz, and other common formats.
 */
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
import { basename, join, dirname, relative } from 'path';
import { SpecializedAgent, } from './types.js';
import { getErrorMessage } from '../../types/index.js';
// ============================================================================
// Configuration
// ============================================================================
const ARCHIVE_AGENT_CONFIG = {
    id: 'archive-agent',
    name: 'Archive Agent',
    description: 'Create, extract, and inspect archive files (zip, tar, gzip)',
    capabilities: ['archive-extract', 'archive-create'],
    fileExtensions: ['zip', 'tar', 'gz', 'tgz', 'tar.gz', 'tar.bz2', '7z', 'rar'],
    maxFileSize: 1024 * 1024 * 1024, // 1GB
    requiredTools: [],
};
// ============================================================================
// Archive Agent Implementation
// ============================================================================
export class ArchiveAgent extends SpecializedAgent {
    jszip = null;
    tar = null;
    constructor() {
        super(ARCHIVE_AGENT_CONFIG);
    }
    async initialize() {
        // Best-effort preload. Individual actions also lazy-load on demand.
        await this.ensureJszip();
        await this.ensureTar();
        this.isInitialized = true;
        this.emit('initialized', {
            jszip: !!this.jszip,
            tar: !!this.tar,
        });
    }
    getSupportedActions() {
        return ['list', 'extract', 'create', 'info', 'add', 'remove'];
    }
    getActionHelp(action) {
        const help = {
            list: 'List contents of an archive',
            extract: 'Extract files from an archive',
            create: 'Create a new archive',
            info: 'Get information about an archive',
            add: 'Add files to an existing archive',
            remove: 'Remove files from an archive',
        };
        return help[action] || `Unknown action: ${action}`;
    }
    async execute(task) {
        const startTime = Date.now();
        try {
            switch (task.action) {
                case 'list':
                    return await this.listArchive(task, startTime);
                case 'extract':
                    return await this.extractArchive(task, startTime);
                case 'create':
                    return await this.createArchive(task, startTime);
                case 'info':
                    return await this.getArchiveInfo(task, startTime);
                case 'add':
                    return await this.addToArchive(task, startTime);
                case 'remove':
                    return await this.removeFromArchive(task, startTime);
                default:
                    return { success: false, error: `Unknown action: ${task.action}` };
            }
        }
        catch (error) {
            return {
                success: false,
                error: `Archive error: ${getErrorMessage(error)}`,
                duration: Date.now() - startTime,
            };
        }
    }
    // ============================================================================
    // Actions
    // ============================================================================
    async listArchive(task, startTime) {
        if (!task.inputFiles || task.inputFiles.length === 0) {
            return { success: false, error: 'No archive file specified' };
        }
        const archivePath = task.inputFiles[0];
        if (!existsSync(archivePath)) {
            return { success: false, error: `Archive not found: ${archivePath}` };
        }
        const format = this.detectFormat(archivePath);
        let entries;
        switch (format) {
            case 'zip':
                entries = await this.listZip(archivePath);
                break;
            case 'tar':
            case 'tar.gz':
            case 'tgz':
                entries = await this.listTar(archivePath, format !== 'tar');
                break;
            default:
                return { success: false, error: `Unsupported format: ${format}` };
        }
        return {
            success: true,
            data: entries,
            output: this.formatEntryList(entries),
            duration: Date.now() - startTime,
            metadata: {
                format,
                entryCount: entries.length,
                totalSize: entries.reduce((sum, e) => sum + e.size, 0),
            },
        };
    }
    async extractArchive(task, startTime) {
        if (!task.inputFiles || task.inputFiles.length === 0) {
            return { success: false, error: 'No archive file specified' };
        }
        const archivePath = task.inputFiles[0];
        if (!existsSync(archivePath)) {
            return { success: false, error: `Archive not found: ${archivePath}` };
        }
        const options = (task.params || {});
        const outputDir = options.outputDir || dirname(archivePath);
        // Create output directory
        if (!existsSync(outputDir)) {
            mkdirSync(outputDir, { recursive: true });
        }
        const format = this.detectFormat(archivePath);
        let extractedFiles;
        switch (format) {
            case 'zip':
                extractedFiles = await this.extractZip(archivePath, outputDir, options);
                break;
            case 'tar':
            case 'tar.gz':
            case 'tgz':
                extractedFiles = await this.extractTar(archivePath, outputDir, format !== 'tar', options);
                break;
            default:
                return { success: false, error: `Unsupported format: ${format}` };
        }
        return {
            success: true,
            data: { extractedFiles, outputDir },
            output: `Extracted ${extractedFiles.length} files to ${outputDir}`,
            duration: Date.now() - startTime,
            metadata: {
                format,
                fileCount: extractedFiles.length,
            },
        };
    }
    async createArchive(task, startTime) {
        if (!task.outputFile) {
            return { success: false, error: 'No output file specified' };
        }
        if (!task.inputFiles || task.inputFiles.length === 0) {
            return { success: false, error: 'No input files specified' };
        }
        const options = (task.params || {});
        const format = options.format || this.detectFormat(task.outputFile);
        // Collect all files to archive
        const files = this.collectFiles(task.inputFiles, options);
        if (files.length === 0) {
            return { success: false, error: 'No files to archive' };
        }
        switch (format) {
            case 'zip':
                await this.createZip(task.outputFile, files, options);
                break;
            case 'tar':
            case 'tar.gz':
            case 'tgz':
                await this.createTar(task.outputFile, files, format !== 'tar', options);
                break;
            default:
                return { success: false, error: `Unsupported format: ${format}` };
        }
        const stats = statSync(task.outputFile);
        return {
            success: true,
            outputFile: task.outputFile,
            output: `Created ${task.outputFile} (${this.formatSize(stats.size)}) with ${files.length} files`,
            duration: Date.now() - startTime,
            metadata: {
                format,
                fileCount: files.length,
                archiveSize: stats.size,
            },
        };
    }
    async getArchiveInfo(task, startTime) {
        if (!task.inputFiles || task.inputFiles.length === 0) {
            return { success: false, error: 'No archive file specified' };
        }
        const archivePath = task.inputFiles[0];
        if (!existsSync(archivePath)) {
            return { success: false, error: `Archive not found: ${archivePath}` };
        }
        const stats = statSync(archivePath);
        const format = this.detectFormat(archivePath);
        // Get entries for detailed info
        const listResult = await this.listArchive(task, startTime);
        if (!listResult.success) {
            return listResult;
        }
        const entries = listResult.data;
        const totalUncompressed = entries.reduce((sum, e) => sum + e.size, 0);
        const directories = entries.filter(e => e.isDirectory).length;
        const files = entries.length - directories;
        const info = {
            format,
            filename: basename(archivePath),
            totalSize: totalUncompressed,
            compressedSize: stats.size,
            entryCount: entries.length,
            entries,
        };
        const compressionRatio = totalUncompressed > 0
            ? ((1 - stats.size / totalUncompressed) * 100).toFixed(1)
            : '0';
        return {
            success: true,
            data: info,
            output: this.formatArchiveInfo(info, compressionRatio, files, directories),
            duration: Date.now() - startTime,
        };
    }
    async addToArchive(task, startTime) {
        if (!task.inputFiles || task.inputFiles.length < 2) {
            return { success: false, error: 'Archive file and files to add required' };
        }
        const archivePath = task.inputFiles[0];
        const filesToAdd = task.inputFiles.slice(1);
        if (!existsSync(archivePath)) {
            return { success: false, error: `Archive not found: ${archivePath}` };
        }
        const format = this.detectFormat(archivePath);
        if (format !== 'zip') {
            return { success: false, error: 'Adding files is only supported for ZIP archives' };
        }
        const jszip = await this.ensureJszip();
        if (!jszip) {
            return { success: false, error: 'jszip library required. Install with: npm install jszip' };
        }
        // Read existing archive
        const buffer = readFileSync(archivePath);
        const zip = await jszip.loadAsync(buffer);
        // Add new files
        const files = this.collectFiles(filesToAdd, {});
        for (const { path, fullPath } of files) {
            const content = readFileSync(fullPath);
            zip.file(path, content);
        }
        // Write back
        const output = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
        writeFileSync(archivePath, output);
        return {
            success: true,
            output: `Added ${files.length} files to ${archivePath}`,
            duration: Date.now() - startTime,
        };
    }
    async removeFromArchive(task, startTime) {
        if (!task.inputFiles || task.inputFiles.length === 0) {
            return { success: false, error: 'No archive file specified' };
        }
        const patterns = task.params?.patterns || [];
        if (patterns.length === 0) {
            return { success: false, error: 'Patterns to remove required' };
        }
        const archivePath = task.inputFiles[0];
        if (!existsSync(archivePath)) {
            return { success: false, error: `Archive not found: ${archivePath}` };
        }
        const format = this.detectFormat(archivePath);
        if (format !== 'zip') {
            return { success: false, error: 'Removing files is only supported for ZIP archives' };
        }
        const jszip = await this.ensureJszip();
        if (!jszip) {
            return { success: false, error: 'jszip library required' };
        }
        // Read existing archive
        const buffer = readFileSync(archivePath);
        const zip = await jszip.loadAsync(buffer);
        // Remove matching files
        let removedCount = 0;
        for (const pattern of patterns) {
            const regex = new RegExp(pattern.replace(/\*/g, '.*'));
            zip.forEach((path) => {
                if (regex.test(path)) {
                    zip.remove(path);
                    removedCount++;
                }
            });
        }
        // Write back
        const output = await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
        writeFileSync(archivePath, output);
        return {
            success: true,
            output: `Removed ${removedCount} files from ${archivePath}`,
            duration: Date.now() - startTime,
        };
    }
    // ============================================================================
    // ZIP Operations
    // ============================================================================
    async ensureJszip() {
        if (this.jszip)
            return this.jszip;
        try {
            const jszipModule = await import('jszip');
            this.jszip = jszipModule.default || jszipModule;
        }
        catch (_error) {
            const required = this.tryRequireOptional('jszip');
            if (required) {
                this.jszip = required.default || required;
            }
        }
        return this.jszip;
    }
    async ensureTar() {
        if (this.tar)
            return this.tar;
        try {
            const tarModule = await import('tar');
            this.tar = tarModule;
        }
        catch (_error) {
            const required = this.tryRequireOptional('tar');
            if (required) {
                this.tar = required.default || required;
            }
        }
        return this.tar;
    }
    // Prefer CommonJS-style loading first so Jest module mocks are honored.
    // Falls back to dynamic import for ESM-only runtime environments.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    tryRequireOptional(moduleName) {
        try {
            if (typeof require !== 'function') {
                return null;
            }
            return require(moduleName);
        }
        catch (_error) {
            return null;
        }
    }
    async listZip(archivePath) {
        const jszip = await this.ensureJszip();
        if (!jszip) {
            throw new Error('jszip library required. Install with: npm install jszip');
        }
        const buffer = readFileSync(archivePath);
        const zip = await jszip.loadAsync(buffer);
        const entries = [];
        zip.forEach((path, file) => {
            entries.push({
                path,
                name: basename(path),
                size: file._data?.uncompressedSize || 0,
                compressedSize: file._data?.compressedSize || 0,
                isDirectory: file.dir,
                modifiedDate: file.date,
            });
        });
        return entries;
    }
    async extractZip(archivePath, outputDir, options) {
        const jszip = await this.ensureJszip();
        if (!jszip) {
            throw new Error('jszip library required');
        }
        const buffer = readFileSync(archivePath);
        const zip = await jszip.loadAsync(buffer);
        const extractedFiles = [];
        const filterRegex = options.filterPatterns
            ? new RegExp(options.filterPatterns.map(p => p.replace(/\*/g, '.*')).join('|'))
            : null;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        for (const [path, file] of Object.entries(zip.files)) {
            if (file.dir)
                continue;
            if (filterRegex && !filterRegex.test(path))
                continue;
            const outputPath = join(outputDir, options.preserveStructure !== false ? path : basename(path));
            // Check overwrite
            if (!options.overwrite && existsSync(outputPath)) {
                continue;
            }
            // Create directory
            const dir = dirname(outputPath);
            if (!existsSync(dir)) {
                mkdirSync(dir, { recursive: true });
            }
            // Extract file
            const content = await file.async('nodebuffer');
            writeFileSync(outputPath, content);
            extractedFiles.push(outputPath);
        }
        return extractedFiles;
    }
    async createZip(outputPath, files, options) {
        const jszip = await this.ensureJszip();
        if (!jszip) {
            throw new Error('jszip library required');
        }
        const zip = new jszip();
        for (const { path, fullPath } of files) {
            const content = readFileSync(fullPath);
            zip.file(path, content, {
                compression: 'DEFLATE',
                compressionOptions: { level: options.compressionLevel || 6 },
            });
        }
        const output = await zip.generateAsync({
            type: 'nodebuffer',
            compression: 'DEFLATE',
            compressionOptions: { level: options.compressionLevel || 6 },
        });
        writeFileSync(outputPath, output);
    }
    // ============================================================================
    // TAR Operations
    // ============================================================================
    async listTar(archivePath, gzipped) {
        const tar = await this.ensureTar();
        if (!tar) {
            throw new Error('tar library required. Install with: npm install tar');
        }
        const entries = [];
        await tar.list({
            file: archivePath,
            gzip: gzipped,
            onentry: (entry) => {
                entries.push({
                    path: entry.path,
                    name: basename(entry.path),
                    size: entry.size,
                    isDirectory: entry.type === 'Directory',
                    modifiedDate: entry.mtime,
                    permissions: entry.mode?.toString(8),
                });
            },
        });
        return entries;
    }
    async extractTar(archivePath, outputDir, gzipped, options) {
        const tar = await this.ensureTar();
        if (!tar) {
            throw new Error('tar library required');
        }
        const extractedFiles = [];
        const filterRegex = options.filterPatterns
            ? new RegExp(options.filterPatterns.map(p => p.replace(/\*/g, '.*')).join('|'))
            : null;
        await tar.extract({
            file: archivePath,
            cwd: outputDir,
            gzip: gzipped,
            filter: (path) => {
                if (filterRegex && !filterRegex.test(path))
                    return false;
                extractedFiles.push(join(outputDir, path));
                return true;
            },
            newer: !options.overwrite,
        });
        return extractedFiles;
    }
    async createTar(outputPath, files, gzipped, _options) {
        const tar = await this.ensureTar();
        if (!tar) {
            throw new Error('tar library required');
        }
        // Find common base directory
        const baseDirs = files.map(f => dirname(f.fullPath));
        const commonBase = this.findCommonBase(baseDirs);
        await tar.create({
            file: outputPath,
            gzip: gzipped,
            cwd: commonBase,
            portable: true,
        }, files.map(f => relative(commonBase, f.fullPath)));
    }
    // ============================================================================
    // Helper Methods
    // ============================================================================
    detectFormat(filePath) {
        const name = basename(filePath).toLowerCase();
        if (name.endsWith('.tar.gz') || name.endsWith('.tgz'))
            return 'tar.gz';
        if (name.endsWith('.tar.bz2'))
            return 'tar.bz2';
        if (name.endsWith('.tar'))
            return 'tar';
        if (name.endsWith('.zip'))
            return 'zip';
        if (name.endsWith('.7z'))
            return '7z';
        if (name.endsWith('.rar'))
            return 'rar';
        return 'zip'; // Default
    }
    collectFiles(paths, options) {
        const files = [];
        const excludeRegex = options.excludePatterns
            ? new RegExp(options.excludePatterns.map(p => p.replace(/\*/g, '.*')).join('|'))
            : null;
        const collectDir = (dir, basePath = '') => {
            const entries = readdirSync(dir, { withFileTypes: true });
            for (const entry of entries) {
                const fullPath = join(dir, entry.name);
                const relativePath = basePath ? join(basePath, entry.name) : entry.name;
                // Skip hidden files unless included
                if (!options.includeHidden && entry.name.startsWith('.'))
                    continue;
                // Skip excluded patterns
                if (excludeRegex && excludeRegex.test(relativePath))
                    continue;
                if (entry.isDirectory()) {
                    collectDir(fullPath, relativePath);
                }
                else {
                    files.push({ path: relativePath, fullPath });
                }
            }
        };
        for (const path of paths) {
            if (!existsSync(path))
                continue;
            const stats = statSync(path);
            if (stats.isDirectory()) {
                collectDir(path, basename(path));
            }
            else {
                files.push({ path: basename(path), fullPath: path });
            }
        }
        return files;
    }
    findCommonBase(paths) {
        if (paths.length === 0)
            return process.cwd();
        if (paths.length === 1)
            return paths[0];
        const parts = paths.map(p => p.split('/'));
        const common = [];
        for (let i = 0; i < parts[0].length; i++) {
            const part = parts[0][i];
            if (parts.every(p => p[i] === part)) {
                common.push(part);
            }
            else {
                break;
            }
        }
        return common.join('/') || '/';
    }
    formatSize(bytes) {
        if (bytes < 1024)
            return `${bytes} B`;
        if (bytes < 1024 * 1024)
            return `${(bytes / 1024).toFixed(1)} KB`;
        if (bytes < 1024 * 1024 * 1024)
            return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
        return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
    }
    formatEntryList(entries) {
        const lines = [
            'Archive Contents:',
            '─'.repeat(70),
            'Size'.padEnd(12) + 'Modified'.padEnd(22) + 'Name',
            '─'.repeat(70),
        ];
        for (const entry of entries.slice(0, 50)) {
            const size = entry.isDirectory ? '<DIR>' : this.formatSize(entry.size);
            const date = entry.modifiedDate
                ? new Date(entry.modifiedDate).toISOString().slice(0, 19)
                : '-'.repeat(19);
            lines.push(`${size.padEnd(12)}${date.padEnd(22)}${entry.path}`);
        }
        if (entries.length > 50) {
            lines.push(`... and ${entries.length - 50} more entries`);
        }
        return lines.join('\n');
    }
    formatArchiveInfo(info, compressionRatio, files, directories) {
        return [
            '┌─────────────────────────────────────────┐',
            '│          ARCHIVE INFORMATION            │',
            '├─────────────────────────────────────────┤',
            `│ File: ${info.filename.slice(0, 32).padEnd(32)}│`,
            `│ Format: ${info.format.toUpperCase().padEnd(30)}│`,
            `│ Compressed: ${this.formatSize(info.compressedSize).padEnd(26)}│`,
            `│ Uncompressed: ${this.formatSize(info.totalSize).padEnd(24)}│`,
            `│ Compression: ${(compressionRatio + '%').padEnd(25)}│`,
            `│ Files: ${String(files).padEnd(31)}│`,
            `│ Directories: ${String(directories).padEnd(25)}│`,
            '└─────────────────────────────────────────┘',
        ].join('\n');
    }
}
// ============================================================================
// Factory
// ============================================================================
let archiveAgentInstance = null;
export function getArchiveAgent() {
    if (!archiveAgentInstance) {
        archiveAgentInstance = new ArchiveAgent();
    }
    return archiveAgentInstance;
}
export async function createArchiveAgent() {
    const agent = getArchiveAgent();
    if (!agent.isReady()) {
        await agent.initialize();
    }
    return agent;
}
//# sourceMappingURL=archive-agent.js.map