import fsPromises from 'fs/promises';
import path from 'path';
import fg from 'fast-glob';
import ignore from 'ignore';
const glob = fg.glob || fg;
const DEFAULT_OPTIONS = {
    respectGitignore: true,
    maxFileSize: 100 * 1024,
    maxTotalSize: 1024 * 1024,
    includeHidden: false,
    compressWhitespace: true,
    removeLockFiles: true,
    removeComments: false,
};
const ALWAYS_EXCLUDE = [
    '**/.git/**',
    '**/node_modules/**',
    '**/.env',
    '**/.env.*',
    '**/credentials.json',
    '**/secrets.json',
    '**/*.pem',
    '**/*.key',
    '**/id_rsa*',
    '**/.ssh/**',
    '**/package-lock.json',
    '**/bun.lockb',
    '**/bun.lock',
    '**/yarn.lock',
    '**/pnpm-lock.yaml',
    '**/composer.lock',
    '**/Gemfile.lock',
    '**/Cargo.lock',
    '**/poetry.lock',
    '**/*.min.js',
    '**/*.min.css',
    '**/dist/**',
    '**/build/**',
    '**/.next/**',
    '**/.nuxt/**',
    '**/coverage/**',
    '**/__pycache__/**',
    '**/*.pyc',
    '**/.pytest_cache/**',
    '**/.DS_Store',
    '**/Thumbs.db',
];
const LANGUAGE_MAP = {
    '.ts': 'typescript',
    '.tsx': 'tsx',
    '.js': 'javascript',
    '.jsx': 'jsx',
    '.py': 'python',
    '.rb': 'ruby',
    '.go': 'go',
    '.rs': 'rust',
    '.java': 'java',
    '.kt': 'kotlin',
    '.swift': 'swift',
    '.c': 'c',
    '.cpp': 'cpp',
    '.h': 'c',
    '.hpp': 'cpp',
    '.cs': 'csharp',
    '.php': 'php',
    '.vue': 'vue',
    '.svelte': 'svelte',
    '.html': 'html',
    '.css': 'css',
    '.scss': 'scss',
    '.sass': 'sass',
    '.less': 'less',
    '.json': 'json',
    '.yaml': 'yaml',
    '.yml': 'yaml',
    '.toml': 'toml',
    '.xml': 'xml',
    '.md': 'markdown',
    '.sql': 'sql',
    '.sh': 'bash',
    '.bash': 'bash',
    '.zsh': 'zsh',
    '.fish': 'fish',
    '.ps1': 'powershell',
    '.dockerfile': 'dockerfile',
    '.tf': 'terraform',
    '.proto': 'protobuf',
    '.graphql': 'graphql',
    '.gql': 'graphql',
};
export class ContextLoader {
    workingDirectory;
    options;
    gitignore = null;
    gitignoreLoaded = false;
    constructor(workingDirectory = process.cwd(), options = {}) {
        this.workingDirectory = workingDirectory;
        this.options = { ...DEFAULT_OPTIONS, ...options };
        // gitignore will be loaded lazily on first loadFiles() call
    }
    async ensureGitignoreLoaded() {
        if (!this.gitignoreLoaded && this.options.respectGitignore) {
            await this.loadGitignore();
            this.gitignoreLoaded = true;
        }
    }
    async loadGitignore() {
        const gitignorePath = path.join(this.workingDirectory, '.gitignore');
        try {
            await fsPromises.access(gitignorePath);
            const content = await fsPromises.readFile(gitignorePath, 'utf-8');
            this.gitignore = ignore().add(content);
        }
        catch (_error) {
            // Ignore errors - file doesn't exist or not readable
        }
    }
    shouldIgnore(relativePath) {
        for (const pattern of ALWAYS_EXCLUDE) {
            if (this.matchPattern(relativePath, pattern)) {
                return true;
            }
        }
        if (this.options.excludePatterns) {
            for (const pattern of this.options.excludePatterns) {
                if (this.matchPattern(relativePath, pattern)) {
                    return true;
                }
            }
        }
        if (this.gitignore && this.gitignore.ignores(relativePath)) {
            return true;
        }
        return false;
    }
    matchPattern(filepath, pattern) {
        if (pattern.startsWith('!')) {
            return !this.matchPattern(filepath, pattern.slice(1));
        }
        const regexPattern = pattern
            .replace(/\./g, '\\.')
            .replace(/\*\*/g, '<<<DOUBLESTAR>>>')
            .replace(/\*/g, '[^/]*')
            .replace(/<<<DOUBLESTAR>>>/g, '.*');
        return new RegExp('^' + regexPattern + '$').test(filepath) ||
            new RegExp('^' + regexPattern + '$').test(path.basename(filepath));
    }
    async loadFiles(patterns) {
        // Ensure gitignore is loaded before processing files
        await this.ensureGitignoreLoaded();
        const searchPatterns = patterns || this.options.patterns || ['**/*'];
        const files = [];
        let totalSize = 0;
        try {
            const matches = await glob(searchPatterns, {
                cwd: this.workingDirectory,
                dot: this.options.includeHidden,
                onlyFiles: true,
                ignore: ALWAYS_EXCLUDE,
                absolute: false,
            });
            for (const relativePath of matches) {
                if (this.shouldIgnore(relativePath)) {
                    continue;
                }
                const absolutePath = path.join(this.workingDirectory, relativePath);
                try {
                    const stats = await fsPromises.stat(absolutePath);
                    if (stats.size > this.options.maxFileSize) {
                        continue;
                    }
                    if (totalSize + stats.size > this.options.maxTotalSize) {
                        continue;
                    }
                    let content = await fsPromises.readFile(absolutePath, 'utf-8');
                    if (this.options.compressWhitespace) {
                        content = this.compressWhitespace(content);
                    }
                    if (this.options.removeLockFiles && this.isLockFile(relativePath)) {
                        content = '[Lock file content omitted - ' + this.formatBytes(stats.size) + ']';
                    }
                    const ext = path.extname(relativePath).toLowerCase();
                    const language = LANGUAGE_MAP[ext];
                    files.push({
                        path: absolutePath,
                        relativePath,
                        content,
                        mtime: stats.mtimeMs,
                        size: stats.size,
                        language,
                    });
                    totalSize += stats.size;
                }
                catch (_error) {
                    continue;
                }
            }
        }
        catch (_error) {
            // Return empty on error
        }
        return files;
    }
    isLockFile(filepath) {
        const lockPatterns = [
            'package-lock.json',
            'bun.lockb',
            'bun.lock',
            'yarn.lock',
            'pnpm-lock.yaml',
            'composer.lock',
            'Gemfile.lock',
            'Cargo.lock',
            'poetry.lock',
        ];
        const basename = path.basename(filepath);
        return lockPatterns.includes(basename);
    }
    compressWhitespace(content) {
        content = content.replace(/[ \t]+$/gm, '');
        content = content.replace(/\n{3,}/g, '\n\n');
        return content;
    }
    formatBytes(bytes) {
        if (bytes < 1024)
            return bytes + ' B';
        if (bytes < 1024 * 1024)
            return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
    }
    static parsePatternString(patternStr) {
        const parts = patternStr.split(',').map(p => p.trim()).filter(p => p);
        const include = [];
        const exclude = [];
        for (const part of parts) {
            if (part.startsWith('!')) {
                exclude.push(part.slice(1));
            }
            else {
                include.push(part);
            }
        }
        return { include, exclude };
    }
    formatForPrompt(files) {
        const parts = ['CONTEXT FILES:'];
        for (const file of files) {
            parts.push('\n--- ' + file.relativePath + ' ---');
            if (file.language) {
                parts.push('```' + file.language);
                parts.push(file.content);
                parts.push('```');
            }
            else {
                parts.push(file.content);
            }
        }
        return parts.join('\n');
    }
    getSummary(files) {
        const totalSize = files.reduce((sum, f) => sum + f.size, 0);
        const languages = [...new Set(files.map(f => f.language).filter(Boolean))];
        const fileList = files.map(f => f.relativePath).slice(0, 5).join(', ');
        const moreCount = files.length > 5 ? ', +' + (files.length - 5) + ' more' : '';
        return '📁 Context: ' + files.length + ' files (' + this.formatBytes(totalSize) + ')\n' +
            'Languages: ' + (languages.join(', ') || 'mixed') + '\n' +
            'Files: ' + fileList + moreCount;
    }
}
let contextLoaderInstance = null;
export function getContextLoader(workingDirectory, options) {
    if (!contextLoaderInstance || workingDirectory) {
        contextLoaderInstance = new ContextLoader(workingDirectory, options);
    }
    return contextLoaderInstance;
}
export function resetContextLoader() {
    contextLoaderInstance = null;
}
//# sourceMappingURL=context-loader.js.map