/**
 * Codebase Explorer Service
 *
 * Provides comprehensive codebase exploration capabilities including:
 * - Directory structure visualization
 * - File statistics and metrics
 * - Language detection and distribution
 * - Project type detection
 * - Smart file recommendations
 *
 * Based on hurry-mode's codebase exploration features.
 */
import * as fsPromises from "fs/promises";
import * as path from "path";
/**
 * Supported languages and their extensions
 */
export const LANGUAGE_EXTENSIONS = {
    typescript: [".ts", ".tsx", ".mts", ".cts"],
    javascript: [".js", ".jsx", ".mjs", ".cjs"],
    python: [".py", ".pyw", ".pyi"],
    go: [".go"],
    rust: [".rs"],
    java: [".java"],
    csharp: [".cs"],
    cpp: [".cpp", ".cc", ".cxx", ".c", ".h", ".hpp"],
    ruby: [".rb", ".rake"],
    php: [".php"],
    swift: [".swift"],
    kotlin: [".kt", ".kts"],
    scala: [".scala"],
    html: [".html", ".htm"],
    css: [".css", ".scss", ".sass", ".less"],
    sql: [".sql"],
    shell: [".sh", ".bash", ".zsh"],
    yaml: [".yaml", ".yml"],
    json: [".json"],
    markdown: [".md", ".mdx"],
    xml: [".xml"],
};
/**
 * Default exploration options
 */
const DEFAULT_OPTIONS = {
    maxDepth: 20,
    includeHidden: false,
    excludePatterns: [
        "node_modules",
        ".git",
        "dist",
        "build",
        "coverage",
        "__pycache__",
        ".next",
        ".nuxt",
        "vendor",
        "target",
    ],
    followSymlinks: false,
    countLines: true,
};
/**
 * Codebase Explorer Service
 */
export class CodebaseExplorer {
    options;
    rootPath;
    files = [];
    directories = [];
    projectInfo = null;
    constructor(rootPath, options = {}) {
        this.rootPath = path.resolve(rootPath);
        this.options = { ...DEFAULT_OPTIONS, ...options };
    }
    /**
     * Explore the codebase
     */
    async explore() {
        this.files = [];
        this.directories = [];
        await this.scanDirectory(this.rootPath, 0);
        this.projectInfo = await this.detectProject();
        return this.calculateStats();
    }
    /**
     * Scan a directory recursively
     */
    async scanDirectory(dirPath, depth) {
        if (this.options.maxDepth && depth > this.options.maxDepth) {
            return;
        }
        try {
            const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
            let fileCount = 0;
            let directoryCount = 0;
            let totalSize = 0;
            for (const entry of entries) {
                const fullPath = path.join(dirPath, entry.name);
                const relativePath = path.relative(this.rootPath, fullPath);
                // Check if hidden
                const isHidden = entry.name.startsWith(".");
                if (isHidden && !this.options.includeHidden) {
                    continue;
                }
                // Check exclude patterns
                if (this.shouldExclude(relativePath, entry.name)) {
                    continue;
                }
                if (entry.isDirectory()) {
                    directoryCount++;
                    await this.scanDirectory(fullPath, depth + 1);
                }
                else if (entry.isFile()) {
                    fileCount++;
                    const fileInfo = await this.analyzeFile(fullPath, relativePath);
                    if (fileInfo) {
                        this.files.push(fileInfo);
                        totalSize += fileInfo.size;
                    }
                }
            }
            this.directories.push({
                path: path.relative(this.rootPath, dirPath) || ".",
                name: path.basename(dirPath),
                fileCount,
                directoryCount,
                totalSize,
                depth,
                isHidden: path.basename(dirPath).startsWith("."),
            });
        }
        catch (_error) {
            // Skip directories we can't read
        }
    }
    /**
     * Analyze a single file
     */
    async analyzeFile(fullPath, relativePath) {
        try {
            const stats = await fsPromises.stat(fullPath);
            const ext = path.extname(fullPath).toLowerCase();
            const name = path.basename(fullPath);
            const language = this.detectLanguage(ext);
            const category = this.categorizeFile(relativePath, name, ext);
            let lines = 0;
            if (this.options.countLines && this.isTextFile(ext)) {
                try {
                    const content = await fsPromises.readFile(fullPath, "utf-8");
                    lines = content.split("\n").length;
                }
                catch {
                    // Binary file or encoding issue
                }
            }
            return {
                path: relativePath,
                name,
                extension: ext,
                language,
                category,
                size: stats.size,
                lines,
                modifiedAt: stats.mtime,
                isHidden: name.startsWith("."),
            };
        }
        catch {
            return null;
        }
    }
    /**
     * Detect language from extension
     */
    detectLanguage(ext) {
        for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
            if (extensions.includes(ext)) {
                return lang;
            }
        }
        return "unknown";
    }
    /**
     * Categorize a file
     */
    categorizeFile(relativePath, name, ext) {
        const pathLower = relativePath.toLowerCase();
        const nameLower = name.toLowerCase();
        // Test files
        if (pathLower.includes("test") ||
            pathLower.includes("spec") ||
            pathLower.includes("__tests__") ||
            nameLower.includes(".test.") ||
            nameLower.includes(".spec.")) {
            return "test";
        }
        // Config files
        if (nameLower.startsWith(".") ||
            nameLower.includes("config") ||
            nameLower.includes("rc") ||
            [".json", ".yaml", ".yml", ".toml", ".ini"].includes(ext)) {
            if (!pathLower.includes("src/")) {
                return "config";
            }
        }
        // Documentation
        if (ext === ".md" ||
            ext === ".mdx" ||
            ext === ".txt" ||
            ext === ".rst" ||
            pathLower.includes("docs/") ||
            pathLower.includes("documentation/")) {
            return "documentation";
        }
        // Assets
        if ([".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp"].includes(ext) ||
            [".mp3", ".mp4", ".wav", ".webm"].includes(ext) ||
            [".woff", ".woff2", ".ttf", ".eot"].includes(ext)) {
            return "asset";
        }
        // Build artifacts
        if (pathLower.includes("dist/") ||
            pathLower.includes("build/") ||
            pathLower.includes("out/") ||
            ext === ".map") {
            return "build";
        }
        // Source code
        const sourceLangs = [
            "typescript",
            "javascript",
            "python",
            "go",
            "rust",
            "java",
            "csharp",
            "cpp",
            "ruby",
            "php",
            "swift",
            "kotlin",
            "scala",
        ];
        if (sourceLangs.includes(this.detectLanguage(ext))) {
            return "source";
        }
        return "other";
    }
    /**
     * Check if a file is a text file
     */
    isTextFile(ext) {
        const textExtensions = [
            ".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java",
            ".cs", ".cpp", ".c", ".h", ".rb", ".php", ".swift", ".kt",
            ".scala", ".html", ".css", ".scss", ".sass", ".less", ".sql",
            ".sh", ".bash", ".yaml", ".yml", ".json", ".md", ".mdx",
            ".xml", ".txt", ".env", ".gitignore", ".dockerignore",
        ];
        return textExtensions.includes(ext);
    }
    /**
     * Check if a path should be excluded
     */
    shouldExclude(relativePath, name) {
        for (const pattern of this.options.excludePatterns || []) {
            if (name === pattern || relativePath.includes(pattern)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Helper to check if a file exists asynchronously
     */
    async fileExists(filePath) {
        try {
            await fsPromises.access(filePath);
            return true;
        }
        catch {
            return false;
        }
    }
    /**
     * Detect project type and information
     */
    async detectProject() {
        const info = {
            type: "unknown",
            name: path.basename(this.rootPath),
            entryPoints: [],
            configFiles: [],
            dependencies: 0,
            devDependencies: 0,
        };
        // Check for Node.js project
        const packageJsonPath = path.join(this.rootPath, "package.json");
        if (await this.fileExists(packageJsonPath)) {
            try {
                const content = await fsPromises.readFile(packageJsonPath, "utf-8");
                const pkg = JSON.parse(content);
                info.type = "nodejs";
                info.name = pkg.name || info.name;
                info.version = pkg.version;
                info.description = pkg.description;
                info.dependencies = Object.keys(pkg.dependencies || {}).length;
                info.devDependencies = Object.keys(pkg.devDependencies || {}).length;
                info.configFiles.push("package.json");
                if (pkg.main)
                    info.entryPoints.push(pkg.main);
                if (pkg.module)
                    info.entryPoints.push(pkg.module);
            }
            catch {
                // Invalid JSON
            }
        }
        // Check for Python project
        if (await this.fileExists(path.join(this.rootPath, "setup.py")) ||
            await this.fileExists(path.join(this.rootPath, "pyproject.toml"))) {
            info.type = "python";
            info.configFiles.push("setup.py", "pyproject.toml");
        }
        // Check for Go project
        const goModPath = path.join(this.rootPath, "go.mod");
        if (await this.fileExists(goModPath)) {
            info.type = "go";
            info.configFiles.push("go.mod");
            try {
                const goMod = await fsPromises.readFile(goModPath, "utf-8");
                const moduleMatch = goMod.match(/module\s+(.+)/);
                if (moduleMatch) {
                    info.name = moduleMatch[1].trim();
                }
            }
            catch {
                // Can't read go.mod
            }
        }
        // Check for Rust project
        if (await this.fileExists(path.join(this.rootPath, "Cargo.toml"))) {
            info.type = "rust";
            info.configFiles.push("Cargo.toml");
        }
        // Check for Java project
        if (await this.fileExists(path.join(this.rootPath, "pom.xml")) ||
            await this.fileExists(path.join(this.rootPath, "build.gradle"))) {
            info.type = "java";
            info.configFiles.push("pom.xml", "build.gradle");
        }
        // Check for .NET project
        const csprojFiles = this.files.filter((f) => f.extension === ".csproj");
        if (csprojFiles.length > 0) {
            info.type = "dotnet";
            info.configFiles.push(...csprojFiles.map((f) => f.path));
        }
        return info;
    }
    /**
     * Calculate codebase statistics
     */
    calculateStats() {
        const filesByLanguage = new Map();
        const linesByLanguage = new Map();
        const filesByCategory = new Map();
        for (const file of this.files) {
            // By language
            filesByLanguage.set(file.language, (filesByLanguage.get(file.language) || 0) + 1);
            linesByLanguage.set(file.language, (linesByLanguage.get(file.language) || 0) + file.lines);
            // By category
            filesByCategory.set(file.category, (filesByCategory.get(file.category) || 0) + 1);
        }
        // Get largest files
        const largestFiles = [...this.files]
            .sort((a, b) => b.size - a.size)
            .slice(0, 10);
        // Get recently modified
        const recentlyModified = [...this.files]
            .sort((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime())
            .slice(0, 10);
        // Get deepest paths
        const deepestPaths = [...this.files]
            .sort((a, b) => b.path.split("/").length - a.path.split("/").length)
            .slice(0, 5)
            .map((f) => f.path);
        return {
            totalFiles: this.files.length,
            totalDirectories: this.directories.length,
            totalSize: this.files.reduce((sum, f) => sum + f.size, 0),
            totalLines: this.files.reduce((sum, f) => sum + f.lines, 0),
            filesByLanguage,
            linesByLanguage,
            filesByCategory,
            largestFiles,
            recentlyModified,
            deepestPaths,
        };
    }
    /**
     * Get all files
     */
    getFiles() {
        return [...this.files];
    }
    /**
     * Get all directories
     */
    getDirectories() {
        return [...this.directories];
    }
    /**
     * Get project info
     */
    getProjectInfo() {
        return this.projectInfo;
    }
    /**
     * Find files matching a pattern
     */
    findFiles(pattern) {
        const regex = new RegExp(pattern, "i");
        return this.files.filter((f) => regex.test(f.path) || regex.test(f.name));
    }
    /**
     * Find files by language
     */
    findByLanguage(language) {
        return this.files.filter((f) => f.language === language);
    }
    /**
     * Find files by category
     */
    findByCategory(category) {
        return this.files.filter((f) => f.category === category);
    }
    /**
     * Get important files (entry points, configs, main sources)
     */
    getImportantFiles() {
        const important = [];
        // Add config files
        const configNames = [
            "package.json",
            "tsconfig.json",
            "setup.py",
            "pyproject.toml",
            "go.mod",
            "Cargo.toml",
            "pom.xml",
            "build.gradle",
            ".eslintrc.js",
            ".prettierrc",
            "Dockerfile",
            "docker-compose.yml",
        ];
        for (const file of this.files) {
            if (configNames.includes(file.name)) {
                important.push(file);
            }
        }
        // Add main entry files
        const entryNames = [
            "index.ts",
            "index.js",
            "main.ts",
            "main.js",
            "app.ts",
            "app.js",
            "main.py",
            "__init__.py",
            "main.go",
            "main.rs",
            "lib.rs",
            "Main.java",
            "Program.cs",
        ];
        for (const file of this.files) {
            if (entryNames.includes(file.name) && !important.includes(file)) {
                important.push(file);
            }
        }
        return important;
    }
    /**
     * Generate a tree view of the codebase
     */
    async generateTree(maxDepth = 3) {
        const lines = [];
        lines.push(path.basename(this.rootPath));
        const addEntry = async (dirPath, prefix, depth) => {
            if (depth > maxDepth) {
                return;
            }
            try {
                const fullPath = path.join(this.rootPath, dirPath);
                const entries = await fsPromises.readdir(fullPath, { withFileTypes: true });
                // Sort: directories first, then files
                const sorted = entries.sort((a, b) => {
                    if (a.isDirectory() && !b.isDirectory())
                        return -1;
                    if (!a.isDirectory() && b.isDirectory())
                        return 1;
                    return a.name.localeCompare(b.name);
                });
                // Filter excluded
                const filtered = sorted.filter((e) => !this.shouldExclude(path.join(dirPath, e.name), e.name) &&
                    (this.options.includeHidden || !e.name.startsWith(".")));
                for (let i = 0; i < filtered.length; i++) {
                    const entry = filtered[i];
                    const isLast = i === filtered.length - 1;
                    const connector = isLast ? "└── " : "├── ";
                    const childPrefix = isLast ? "    " : "│   ";
                    if (entry.isDirectory()) {
                        lines.push(`${prefix}${connector}${entry.name}/`);
                        await addEntry(path.join(dirPath, entry.name), prefix + childPrefix, depth + 1);
                    }
                    else {
                        lines.push(`${prefix}${connector}${entry.name}`);
                    }
                }
            }
            catch {
                // Skip unreadable directories
            }
        };
        await addEntry("", "", 0);
        return lines.join("\n");
    }
    /**
     * Generate a summary report
     */
    generateReport() {
        const stats = this.calculateStats();
        const lines = [];
        lines.push("═".repeat(60));
        lines.push("📁 CODEBASE EXPLORER REPORT");
        lines.push("═".repeat(60));
        lines.push("");
        if (this.projectInfo) {
            lines.push("─".repeat(40));
            lines.push("PROJECT INFO");
            lines.push("─".repeat(40));
            lines.push(`Name: ${this.projectInfo.name}`);
            lines.push(`Type: ${this.projectInfo.type}`);
            if (this.projectInfo.version) {
                lines.push(`Version: ${this.projectInfo.version}`);
            }
            if (this.projectInfo.description) {
                lines.push(`Description: ${this.projectInfo.description}`);
            }
            lines.push(`Dependencies: ${this.projectInfo.dependencies}`);
            lines.push(`Dev Dependencies: ${this.projectInfo.devDependencies}`);
            lines.push("");
        }
        lines.push("─".repeat(40));
        lines.push("STATISTICS");
        lines.push("─".repeat(40));
        lines.push(`Total Files: ${stats.totalFiles}`);
        lines.push(`Total Directories: ${stats.totalDirectories}`);
        lines.push(`Total Size: ${this.formatSize(stats.totalSize)}`);
        lines.push(`Total Lines: ${stats.totalLines.toLocaleString()}`);
        lines.push("");
        lines.push("─".repeat(40));
        lines.push("FILES BY LANGUAGE");
        lines.push("─".repeat(40));
        const langEntries = Array.from(stats.filesByLanguage.entries())
            .sort((a, b) => b[1] - a[1])
            .slice(0, 10);
        for (const [lang, count] of langEntries) {
            const lines_ = stats.linesByLanguage.get(lang) || 0;
            lines.push(`  ${lang}: ${count} files (${lines_.toLocaleString()} lines)`);
        }
        lines.push("");
        lines.push("─".repeat(40));
        lines.push("FILES BY CATEGORY");
        lines.push("─".repeat(40));
        for (const [category, count] of stats.filesByCategory) {
            lines.push(`  ${category}: ${count} files`);
        }
        lines.push("");
        lines.push("─".repeat(40));
        lines.push("LARGEST FILES");
        lines.push("─".repeat(40));
        for (const file of stats.largestFiles.slice(0, 5)) {
            lines.push(`  ${file.path} (${this.formatSize(file.size)})`);
        }
        lines.push("");
        lines.push("─".repeat(40));
        lines.push("RECENTLY MODIFIED");
        lines.push("─".repeat(40));
        for (const file of stats.recentlyModified.slice(0, 5)) {
            lines.push(`  ${file.path} (${file.modifiedAt.toLocaleString()})`);
        }
        lines.push("");
        lines.push("═".repeat(60));
        return lines.join("\n");
    }
    /**
     * Format file size
     */
    formatSize(bytes) {
        const units = ["B", "KB", "MB", "GB"];
        let size = bytes;
        let unitIndex = 0;
        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }
        return `${size.toFixed(1)} ${units[unitIndex]}`;
    }
}
/**
 * Create a codebase explorer instance
 */
export function createCodebaseExplorer(rootPath, options) {
    return new CodebaseExplorer(rootPath, options);
}
/**
 * Quick explore function
 */
export async function exploreCodebase(rootPath, options) {
    const explorer = createCodebaseExplorer(rootPath, options);
    const stats = await explorer.explore();
    return {
        stats,
        project: explorer.getProjectInfo(),
        tree: await explorer.generateTree(),
        report: explorer.generateReport(),
    };
}
//# sourceMappingURL=codebase-explorer.js.map