/**
 * Vigthoria Codebase Indexer — RAG (Retrieval-Augmented Generation)
 * 
 * Indexes the workspace for semantic code search.
 * Enables "@codebase" queries like Cursor.
 * 
 * Architecture:
 * - File scanner with configurable ignore patterns
 * - Chunking strategy: function/class/block level
 * - TF-IDF based local search (no external embedding service needed)
 * - Results formatted as context for AI prompts
 * - Incremental indexing via file watchers
 */

const vscode = require('vscode');
const path = require('path');
const crypto = require('crypto');

class VigthoriCodebaseIndexer {
    constructor() {
        this.index = new Map();           // filePath -> chunks[]
        this.invertedIndex = new Map();    // term -> Set<chunkId>
        this.chunkStore = new Map();       // chunkId -> chunk
        this.fileHashes = new Map();       // filePath -> hash (for incremental)
        this.isIndexing = false;
        this.indexedFileCount = 0;
        this.totalChunks = 0;
        this.fileWatcher = null;
        this.statusBarItem = null;
        
        // Config
        this.maxFileSize = 1024 * 1024;  // 1MB max per file
        this.chunkSize = 50;              // ~50 lines per chunk
        this.chunkOverlap = 5;            // 5 lines overlap between chunks
        this.maxResults = 10;
        
        // Ignore patterns
        this.ignorePatterns = [
            'node_modules', '.git', 'dist', 'build', 'out', '.next',
            '__pycache__', '.pytest_cache', '.mypy_cache', 'venv', 'env',
            '.DS_Store', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
            '.vsix', '.map', '.min.js', '.min.css', 'coverage',
            '.cache', '.tmp', 'tmp', 'temp', '.idea', '.vscode'
        ];
        
        // Supported file extensions
        this.supportedExtensions = new Set([
            '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rs',
            '.c', '.cpp', '.h', '.hpp', '.cs', '.rb', '.php', '.swift',
            '.kt', '.scala', '.vue', '.svelte', '.astro',
            '.html', '.css', '.scss', '.less', '.sass',
            '.json', '.yaml', '.yml', '.toml', '.xml',
            '.md', '.txt', '.rst', '.sh', '.bash', '.zsh',
            '.sql', '.graphql', '.prisma', '.env',
            '.dockerfile', '.tf', '.hcl'
        ]);

        console.log('[CODEBASE-INDEXER] ✨ Initialized');
    }

    register(context) {
        // Commands
        context.subscriptions.push(
            vscode.commands.registerCommand('vigthoria.indexWorkspace', () => this.indexWorkspace()),
            vscode.commands.registerCommand('vigthoria.searchCodebase', (query) => this.searchAndShow(query)),
            vscode.commands.registerCommand('vigthoria.indexStatus', () => this.showIndexStatus()),
        );

        // Status bar
        this.statusBarItem = vscode.window.createStatusBarItem(
            vscode.StatusBarAlignment.Right, 97
        );
        this.statusBarItem.command = 'vigthoria.indexStatus';
        this.statusBarItem.text = '$(database) Index: --';
        this.statusBarItem.tooltip = 'Vigthoria Codebase Index — Click for status';
        this.statusBarItem.show();
        context.subscriptions.push(this.statusBarItem);

        // File watcher for incremental updates
        if (vscode.workspace.workspaceFolders?.length > 0) {
            this.fileWatcher = vscode.workspace.createFileSystemWatcher('**/*');
            this.fileWatcher.onDidChange((uri) => this._onFileChanged(uri));
            this.fileWatcher.onDidCreate((uri) => this._onFileChanged(uri));
            this.fileWatcher.onDidDelete((uri) => this._onFileDeleted(uri));
            context.subscriptions.push(this.fileWatcher);
        }

        // Auto-index on startup (delayed)
        setTimeout(() => this.indexWorkspace(true), 5000);

        console.log('[CODEBASE-INDEXER] ✅ Registered');
    }

    /**
     * Index the entire workspace
     */
    async indexWorkspace(silent = false) {
        if (this.isIndexing) {
            if (!silent) vscode.window.showInformationMessage('Indexing already in progress...');
            return;
        }

        const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
        if (!workspaceRoot) {
            if (!silent) vscode.window.showWarningMessage('No workspace open');
            return;
        }

        this.isIndexing = true;
        this._updateStatusBar('indexing');
        
        if (!silent) {
            vscode.window.showInformationMessage('🔍 Indexing workspace...');
        }

        const startTime = Date.now();
        
        try {
            // Clear previous index
            this.index.clear();
            this.invertedIndex.clear();
            this.chunkStore.clear();
            this.indexedFileCount = 0;
            this.totalChunks = 0;

            // Find all files
            const files = await this._findFiles(workspaceRoot);
            
            // Index each file
            for (const filePath of files) {
                try {
                    await this._indexFile(filePath);
                } catch (error) {
                    // Skip files that can't be read
                }
            }

            const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
            
            this._updateStatusBar('ready');
            
            if (!silent) {
                vscode.window.showInformationMessage(
                    `✅ Indexed ${this.indexedFileCount} files, ${this.totalChunks} chunks in ${elapsed}s`
                );
            }
            
            console.log(`[CODEBASE-INDEXER] ✅ Indexed ${this.indexedFileCount} files, ${this.totalChunks} chunks in ${elapsed}s`);
            
        } catch (error) {
            console.error('[CODEBASE-INDEXER] Indexing error:', error);
            this._updateStatusBar('error');
        } finally {
            this.isIndexing = false;
        }
    }

    /**
     * Search the codebase for relevant code
     * Returns formatted context suitable for AI prompts
     * 
     * @param {string} query - Natural language query
     * @param {number} maxResults - Max chunks to return
     * @returns {Array} Ranked results with file path, content, score
     */
    search(query, maxResults = this.maxResults) {
        if (this.totalChunks === 0) return [];

        const queryTerms = this._tokenize(query.toLowerCase());
        const scores = new Map();  // chunkId -> score

        // TF-IDF scoring
        for (const term of queryTerms) {
            const matchingChunks = this.invertedIndex.get(term);
            if (!matchingChunks) continue;

            // IDF = log(total chunks / chunks containing term)
            const idf = Math.log(this.totalChunks / matchingChunks.size);

            for (const chunkId of matchingChunks) {
                const chunk = this.chunkStore.get(chunkId);
                if (!chunk) continue;

                // TF = frequency of term in chunk
                const tf = (chunk.terms.get(term) || 0) / chunk.totalTerms;
                const tfidf = tf * idf;

                scores.set(chunkId, (scores.get(chunkId) || 0) + tfidf);
            }
        }

        // Boost for file name/path match
        for (const [chunkId, score] of scores) {
            const chunk = this.chunkStore.get(chunkId);
            if (chunk) {
                const fileName = path.basename(chunk.filePath).toLowerCase();
                for (const term of queryTerms) {
                    if (fileName.includes(term)) {
                        scores.set(chunkId, score * 1.5);  // 50% boost
                    }
                }
            }
        }

        // Sort by score and take top results
        const ranked = Array.from(scores.entries())
            .sort((a, b) => b[1] - a[1])
            .slice(0, maxResults);

        return ranked.map(([chunkId, score]) => {
            const chunk = this.chunkStore.get(chunkId);
            return {
                filePath: chunk.filePath,
                relativePath: chunk.relativePath,
                startLine: chunk.startLine,
                endLine: chunk.endLine,
                content: chunk.content,
                language: chunk.language,
                score: Math.round(score * 1000) / 1000,
                symbol: chunk.symbol || null
            };
        });
    }

    /**
     * Search and format results as AI context string
     */
    getContextForQuery(query, maxTokens = 4000) {
        const results = this.search(query, 15);
        
        if (results.length === 0) {
            return '';
        }

        let context = '### Relevant code from workspace:\n\n';
        let currentTokens = 0;
        const avgCharsPerToken = 4;

        for (const result of results) {
            const entry = `**${result.relativePath}** (lines ${result.startLine}-${result.endLine}):\n\`\`\`${result.language}\n${result.content}\n\`\`\`\n\n`;
            const entryTokens = Math.ceil(entry.length / avgCharsPerToken);

            if (currentTokens + entryTokens > maxTokens) break;
            
            context += entry;
            currentTokens += entryTokens;
        }

        return context;
    }

    /**
     * Interactive search command
     */
    async searchAndShow(query) {
        if (!query) {
            query = await vscode.window.showInputBox({
                prompt: 'Search codebase...',
                placeHolder: 'e.g., authentication middleware, database connection'
            });
        }
        if (!query) return;

        const results = this.search(query);
        
        if (results.length === 0) {
            vscode.window.showInformationMessage('No results found. Try indexing first.');
            return;
        }

        const items = results.map(r => ({
            label: `$(file) ${r.relativePath}`,
            description: `Lines ${r.startLine}-${r.endLine} | Score: ${r.score}`,
            detail: r.content.split('\n')[0].trim().substring(0, 100),
            result: r
        }));

        const selected = await vscode.window.showQuickPick(items, {
            placeHolder: `${results.length} results for "${query}"`,
            matchOnDescription: true,
            matchOnDetail: true
        });

        if (selected) {
            const uri = vscode.Uri.file(selected.result.filePath);
            const doc = await vscode.workspace.openTextDocument(uri);
            const editor = await vscode.window.showTextDocument(doc);
            const range = new vscode.Range(
                new vscode.Position(selected.result.startLine - 1, 0),
                new vscode.Position(selected.result.endLine - 1, 0)
            );
            editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
            editor.selection = new vscode.Selection(range.start, range.end);
        }
    }

    showIndexStatus() {
        const totalSize = Array.from(this.chunkStore.values())
            .reduce((sum, c) => sum + c.content.length, 0);

        vscode.window.showInformationMessage(
            `Codebase Index: ${this.indexedFileCount} files, ${this.totalChunks} chunks, ${(totalSize / 1024).toFixed(0)}KB indexed`
        );
    }

    // --- Private methods ---

    async _findFiles(rootPath) {
        const files = [];
        
        const pattern = new vscode.RelativePattern(rootPath, '**/*');
        const uris = await vscode.workspace.findFiles(
            pattern,
            `**/{${this.ignorePatterns.join(',')}}/**`,
            10000  // max files
        );

        for (const uri of uris) {
            const ext = path.extname(uri.fsPath).toLowerCase();
            if (this.supportedExtensions.has(ext)) {
                files.push(uri.fsPath);
            }
        }

        return files;
    }

    async _indexFile(filePath) {
        try {
            const data = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath));
            const content = Buffer.from(data).toString('utf-8');
            
            if (content.length > this.maxFileSize) return;
            if (content.length === 0) return;

            // Hash for incremental indexing
            const hash = crypto.createHash('md5').update(content).digest('hex');
            if (this.fileHashes.get(filePath) === hash) return;
            this.fileHashes.set(filePath, hash);

            const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath;
            const relativePath = path.relative(workspaceRoot, filePath);
            const language = this._getLanguage(filePath);
            const lines = content.split('\n');

            // Chunk the file
            const chunks = this._chunkFile(lines, filePath, relativePath, language);
            
            // Index each chunk
            const chunkIds = [];
            for (const chunk of chunks) {
                const chunkId = `${relativePath}:${chunk.startLine}-${chunk.endLine}`;
                
                // Tokenize and build term frequencies
                const terms = new Map();
                const tokens = this._tokenize(chunk.content.toLowerCase());
                let totalTerms = 0;
                
                for (const token of tokens) {
                    terms.set(token, (terms.get(token) || 0) + 1);
                    totalTerms++;
                    
                    // Add to inverted index
                    if (!this.invertedIndex.has(token)) {
                        this.invertedIndex.set(token, new Set());
                    }
                    this.invertedIndex.get(token).add(chunkId);
                }

                chunk.terms = terms;
                chunk.totalTerms = totalTerms;
                
                this.chunkStore.set(chunkId, chunk);
                chunkIds.push(chunkId);
                this.totalChunks++;
            }

            this.index.set(filePath, chunkIds);
            this.indexedFileCount++;

        } catch (error) {
            // Silently skip unreadable files
        }
    }

    _chunkFile(lines, filePath, relativePath, language) {
        const chunks = [];
        
        // Try to chunk by functions/classes first
        const symbolChunks = this._chunkBySymbols(lines, filePath, relativePath, language);
        if (symbolChunks.length > 0) {
            return symbolChunks;
        }
        
        // Fallback: fixed-size chunks with overlap
        for (let i = 0; i < lines.length; i += this.chunkSize - this.chunkOverlap) {
            const endLine = Math.min(i + this.chunkSize, lines.length);
            const chunkLines = lines.slice(i, endLine);
            
            if (chunkLines.join('').trim().length === 0) continue;
            
            chunks.push({
                filePath,
                relativePath,
                startLine: i + 1,
                endLine: endLine,
                content: chunkLines.join('\n'),
                language,
                symbol: null
            });
        }
        
        return chunks;
    }

    _chunkBySymbols(lines, filePath, relativePath, language) {
        const chunks = [];
        
        // Regex patterns for function/class detection
        const patterns = {
            javascript: /^(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function|\(|=>)|class\s+(\w+))/,
            typescript: /^(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*[:=]|class\s+(\w+)|interface\s+(\w+))/,
            python: /^(?:def\s+(\w+)|class\s+(\w+)|async\s+def\s+(\w+))/,
            java: /^(?:\s*(?:public|private|protected|static|final|abstract)\s+)*(?:class\s+(\w+)|(?:\w+\s+)?(\w+)\s*\()/,
            go: /^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)/,
            rust: /^(?:pub\s+)?(?:fn\s+(\w+)|struct\s+(\w+)|enum\s+(\w+)|impl\s+(\w+))/,
        };

        const pattern = patterns[language];
        if (!pattern) return [];

        let currentChunkStart = 0;
        let currentSymbol = null;
        let braceDepth = 0;
        let inSymbol = false;

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            const match = pattern.exec(line.trim());
            
            if (match) {
                // Save previous chunk if it exists
                if (inSymbol && i > currentChunkStart) {
                    const chunkLines = lines.slice(currentChunkStart, i);
                    if (chunkLines.join('').trim().length > 0) {
                        chunks.push({
                            filePath, relativePath,
                            startLine: currentChunkStart + 1,
                            endLine: i,
                            content: chunkLines.join('\n'),
                            language,
                            symbol: currentSymbol
                        });
                    }
                }
                
                currentChunkStart = i;
                currentSymbol = match[1] || match[2] || match[3] || match[4] || 'unknown';
                inSymbol = true;
            }
        }

        // Don't forget the last chunk
        if (inSymbol && lines.length > currentChunkStart) {
            const chunkLines = lines.slice(currentChunkStart);
            if (chunkLines.join('').trim().length > 0) {
                chunks.push({
                    filePath, relativePath,
                    startLine: currentChunkStart + 1,
                    endLine: lines.length,
                    content: chunkLines.join('\n'),
                    language,
                    symbol: currentSymbol
                });
            }
        }

        return chunks;
    }

    _tokenize(text) {
        // Split into words, remove short tokens and stop words
        return text
            .replace(/[^a-zA-Z0-9_$]/g, ' ')
            .split(/\s+/)
            .filter(t => t.length > 2)
            .filter(t => !this._isStopWord(t));
    }

    _isStopWord(word) {
        const stopWords = new Set([
            'the', 'and', 'for', 'that', 'this', 'with', 'from', 'are',
            'was', 'were', 'been', 'have', 'has', 'had', 'not', 'but',
            'its', 'can', 'will', 'would', 'could', 'should', 'may',
            'var', 'let', 'const', 'function', 'return', 'true', 'false',
            'null', 'undefined', 'new', 'class', 'import', 'export',
            'require', 'module', 'exports', 'default', 'async', 'await',
            'try', 'catch', 'throw', 'finally', 'else', 'elif', 'def',
        ]);
        return stopWords.has(word);
    }

    _getLanguage(filePath) {
        const ext = path.extname(filePath).toLowerCase();
        const map = {
            '.js': 'javascript', '.jsx': 'javascript',
            '.ts': 'typescript', '.tsx': 'typescript',
            '.py': 'python', '.java': 'java', '.go': 'go',
            '.rs': 'rust', '.c': 'c', '.cpp': 'cpp', '.h': 'c',
            '.cs': 'csharp', '.rb': 'ruby', '.php': 'php',
            '.swift': 'swift', '.kt': 'kotlin', '.scala': 'scala',
            '.vue': 'vue', '.svelte': 'svelte',
            '.html': 'html', '.css': 'css', '.scss': 'scss',
            '.json': 'json', '.yaml': 'yaml', '.yml': 'yaml',
            '.md': 'markdown', '.sh': 'shell', '.bash': 'shell',
            '.sql': 'sql', '.graphql': 'graphql',
        };
        return map[ext] || 'text';
    }

    _onFileChanged(uri) {
        const ext = path.extname(uri.fsPath).toLowerCase();
        if (this.supportedExtensions.has(ext) && !this._shouldIgnore(uri.fsPath)) {
            // Re-index the changed file
            this._indexFile(uri.fsPath).catch(() => {});
        }
    }

    _onFileDeleted(uri) {
        const filePath = uri.fsPath;
        const chunkIds = this.index.get(filePath);
        if (chunkIds) {
            for (const id of chunkIds) {
                this.chunkStore.delete(id);
                this.totalChunks--;
            }
            this.index.delete(filePath);
            this.fileHashes.delete(filePath);
            this.indexedFileCount--;
        }
    }

    _shouldIgnore(filePath) {
        const lowerPath = filePath.toLowerCase();
        return this.ignorePatterns.some(p => lowerPath.includes(p));
    }

    _updateStatusBar(state) {
        if (!this.statusBarItem) return;
        
        switch (state) {
            case 'indexing':
                this.statusBarItem.text = '$(loading~spin) Indexing...';
                this.statusBarItem.tooltip = 'Indexing workspace files...';
                break;
            case 'ready':
                this.statusBarItem.text = `$(database) ${this.indexedFileCount} files`;
                this.statusBarItem.tooltip = `Vigthoria Index: ${this.indexedFileCount} files, ${this.totalChunks} chunks`;
                break;
            case 'error':
                this.statusBarItem.text = '$(error) Index error';
                setTimeout(() => this._updateStatusBar('ready'), 5000);
                break;
        }
    }

    dispose() {
        if (this.fileWatcher) this.fileWatcher.dispose();
        if (this.statusBarItem) this.statusBarItem.dispose();
        this.index.clear();
        this.invertedIndex.clear();
        this.chunkStore.clear();
    }
}

module.exports = { VigthoriCodebaseIndexer };
