"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SanitizeCommand = void 0;
const chalk_1 = __importDefault(require("chalk"));
const path_1 = __importDefault(require("path"));
const fs = __importStar(require("fs-extra"));
const glob_1 = require("glob");
const spinner_1 = require("../utils/spinner");
class SanitizeCommand {
    constructor() {
        this.spinner = new spinner_1.EnhancedSpinner("Sanitizing project...");
    }
    async execute(target, options) {
        const { fix = false, verbose = false, checkDuplicates = true, checkUnreachable = true, checkRedundancy = true, checkUnused = true, } = options;
        console.log(chalk_1.default.blue.bold("🧹 Project Sanitization\n"));
        try {
            const projectPath = path_1.default.resolve(target);
            if (!(await this.isValidProject(projectPath))) {
                throw new Error("Not a valid project directory");
            }
            this.spinner.start().updateText("Analyzing project structure...");
            const report = await this.analyzeProject(projectPath, {
                checkDuplicates,
                checkUnreachable,
                checkRedundancy,
                checkUnused,
                verbose,
            });
            this.spinner.succeed("Analysis completed");
            this.displayReport(report, verbose);
            if (fix) {
                await this.fixIssues(report, projectPath);
            }
            else {
                this.showFixSuggestions(report);
            }
        }
        catch (error) {
            this.spinner.fail("Sanitization failed");
            throw error;
        }
    }
    async isValidProject(projectPath) {
        const packageJsonPath = path_1.default.join(projectPath, "package.json");
        return await fs.pathExists(packageJsonPath);
    }
    async analyzeProject(projectPath, options) {
        const report = {
            duplicates: [],
            unreachable: [],
            redundant: [],
            unused: [],
            summary: {
                totalIssues: 0,
                duplicates: 0,
                unreachable: 0,
                redundant: 0,
                unused: 0,
            },
        };
        // Get all relevant files
        const files = await this.getAllFiles(projectPath);
        if (options.checkDuplicates) {
            this.spinner.updateText("Checking for duplicate files...");
            report.duplicates = await this.findDuplicates(files);
            report.summary.duplicates = report.duplicates.length;
        }
        if (options.checkUnreachable) {
            this.spinner.updateText("Checking for unreachable code...");
            report.unreachable = await this.findUnreachableCode(files, projectPath);
            report.summary.unreachable = report.unreachable.length;
        }
        if (options.checkRedundancy) {
            this.spinner.updateText("Checking for redundant files...");
            report.redundant = await this.findRedundantFiles(files, projectPath);
            report.summary.redundant = report.redundant.length;
        }
        if (options.checkUnused) {
            this.spinner.updateText("Checking for unused files...");
            report.unused = await this.findUnusedFiles(files, projectPath);
            report.summary.unused = report.unused.length;
        }
        report.summary.totalIssues =
            report.summary.duplicates +
                report.summary.unreachable +
                report.summary.redundant +
                report.summary.unused;
        return report;
    }
    async getAllFiles(projectPath) {
        const patterns = [
            "**/*.{ts,tsx,js,jsx,json,md}",
            "!node_modules/**",
            "!.git/**",
            "!.next/**",
            "!dist/**",
            "!build/**",
            "!.mycontext/**",
        ];
        const files = [];
        for (const pattern of patterns) {
            const matches = await (0, glob_1.glob)(pattern, { cwd: projectPath });
            files.push(...matches.map((file) => path_1.default.join(projectPath, file)));
        }
        return files;
    }
    async findDuplicates(files) {
        const duplicates = [];
        const fileHashes = new Map();
        for (const file of files) {
            try {
                const content = await fs.readFile(file, "utf-8");
                const hash = this.hashContent(content);
                if (fileHashes.has(hash)) {
                    fileHashes.get(hash).push(file);
                }
                else {
                    fileHashes.set(hash, [file]);
                }
            }
            catch (error) {
                // Skip files that can't be read
                continue;
            }
        }
        for (const [hash, fileList] of fileHashes) {
            if (fileList.length > 1) {
                const firstFile = fileList[0];
                if (!firstFile)
                    continue;
                const stats = await fs.stat(firstFile);
                const size = stats.size;
                duplicates.push({
                    type: "exact",
                    files: fileList,
                    similarity: 100,
                    size,
                });
            }
        }
        return duplicates;
    }
    async findUnreachableCode(files, projectPath) {
        const unreachable = [];
        const importMap = new Map();
        const exportMap = new Map();
        // Build import/export maps
        for (const file of files) {
            if (file.endsWith(".ts") ||
                file.endsWith(".tsx") ||
                file.endsWith(".js") ||
                file.endsWith(".jsx")) {
                try {
                    const content = await fs.readFile(file, "utf-8");
                    const relativePath = path_1.default.relative(projectPath, file);
                    // Find imports
                    const imports = this.extractImports(content);
                    importMap.set(relativePath, new Set(imports));
                    // Find exports
                    const exports = this.extractExports(content);
                    exportMap.set(relativePath, new Set(exports));
                }
                catch (error) {
                    continue;
                }
            }
        }
        // Ensure we can resolve aliases from tsconfig if available
        let tsconfigPaths = {};
        let baseUrl = ".";
        const tsconfigPath = path_1.default.join(projectPath, "tsconfig.json");
        if (await fs.pathExists(tsconfigPath)) {
            try {
                // Use a more robust parser or basic cleanup
                const rawTsconfig = await fs.readFile(tsconfigPath, "utf-8");
                // Remove comments (both // and /* */)
                const cleanTsconfig = rawTsconfig.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m);
                const parsedTsconfig = JSON.parse(cleanTsconfig);
                if (parsedTsconfig.compilerOptions) {
                    if (parsedTsconfig.compilerOptions.paths) {
                        tsconfigPaths = parsedTsconfig.compilerOptions.paths;
                    }
                    if (parsedTsconfig.compilerOptions.baseUrl) {
                        baseUrl = parsedTsconfig.compilerOptions.baseUrl;
                    }
                }
            }
            catch (err) {
                // ignore parsing errors
            }
        }
        const resolveImportPath = (importStr) => {
            // Direct matches
            const possiblePaths = [
                importStr,
                importStr.replace(/\.(ts|tsx|js|jsx)$/, ""),
            ];
            // Resolve aliases
            for (const [aliasPattern, destPaths] of Object.entries(tsconfigPaths)) {
                const hasWildcard = aliasPattern.endsWith("/*");
                const aliasBase = hasWildcard ? aliasPattern.slice(0, -2) : aliasPattern;
                if (hasWildcard) {
                    if (importStr.startsWith(aliasBase)) {
                        let subPath = importStr.slice(aliasBase.length);
                        if (subPath.startsWith("/"))
                            subPath = subPath.slice(1);
                        for (const dest of destPaths) {
                            const destBase = dest.replace("/*", "");
                            const resolved = path_1.default.join(baseUrl, destBase, subPath);
                            let normalized = resolved.replace(/\\/g, "/");
                            if (normalized.startsWith("./"))
                                normalized = normalized.slice(2);
                            if (normalized.startsWith("/"))
                                normalized = normalized.slice(1);
                            possiblePaths.push(normalized);
                        }
                    }
                }
                else if (importStr === aliasPattern) {
                    for (const dest of destPaths) {
                        const resolved = path_1.default.join(baseUrl, dest);
                        let normalized = resolved.replace(/\\/g, "/");
                        if (normalized.startsWith("./"))
                            normalized = normalized.slice(2);
                        if (normalized.startsWith("/"))
                            normalized = normalized.slice(1);
                        possiblePaths.push(normalized);
                    }
                }
            }
            // Also handle baseUrl direct imports if not relative
            if (!importStr.startsWith(".") && !importStr.startsWith("/")) {
                const baseUrlResolved = path_1.default.join(baseUrl, importStr);
                possiblePaths.push(baseUrlResolved.replace(/\\/g, "/").replace(/^\.\//, ""));
            }
            return possiblePaths;
        };
        // Check for unreachable files
        for (const file of files) {
            const relativePath = path_1.default.relative(projectPath, file);
            // Skip entry points and common Next.js special files
            if (this.isEntryPoint(relativePath)) {
                continue;
            }
            const relativeWithoutExt = relativePath.replace(/\.(ts|tsx|js|jsx)$/, "");
            // Check if file is imported anywhere
            let isImported = false;
            for (const [_, imports] of importMap) {
                for (const importStr of imports) {
                    const possiblePaths = resolveImportPath(importStr);
                    if (possiblePaths.some(p => p === relativePath || p === relativeWithoutExt || relativePath.endsWith(p))) {
                        isImported = true;
                        break;
                    }
                }
                if (isImported)
                    break;
            }
            if (!isImported) {
                unreachable.push({
                    file: relativePath,
                    reason: "no-imports",
                    details: "File is not imported by any other file",
                });
            }
        }
        return unreachable;
    }
    async findRedundantFiles(files, projectPath) {
        const redundant = [];
        const fileNames = new Map();
        // Group files by name
        for (const file of files) {
            const fileName = path_1.default.basename(file);
            if (!fileNames.has(fileName)) {
                fileNames.set(fileName, []);
            }
            fileNames.get(fileName).push(file);
        }
        // Check for files with same name in different locations
        for (const [fileName, fileList] of fileNames) {
            if (fileList.length > 1) {
                for (const file of fileList) {
                    redundant.push({
                        file: path_1.default.relative(projectPath, file),
                        reason: "duplicate-functionality",
                        details: `Multiple files with name '${fileName}' found`,
                        suggestion: "Consider consolidating or renaming files",
                    });
                }
            }
        }
        // Check for common redundant patterns
        for (const file of files) {
            const relativePath = path_1.default.relative(projectPath, file);
            // Check for test files without corresponding source files
            if (file.includes(".test.") || file.includes(".spec.")) {
                const sourceFile = file.replace(/\.(test|spec)\./, ".");
                if (!(await fs.pathExists(sourceFile))) {
                    redundant.push({
                        file: relativePath,
                        reason: "obsolete",
                        details: "Test file without corresponding source file",
                        suggestion: "Remove test file or create source file",
                    });
                }
            }
            // Check for backup files
            if (file.endsWith(".bak") ||
                file.endsWith(".backup") ||
                file.endsWith("~")) {
                redundant.push({
                    file: relativePath,
                    reason: "obsolete",
                    details: "Backup file detected",
                    suggestion: "Remove backup file if no longer needed",
                });
            }
        }
        return redundant;
    }
    async findUnusedFiles(files, projectPath) {
        const unused = [];
        const packageJsonPath = path_1.default.join(projectPath, "package.json");
        if (await fs.pathExists(packageJsonPath)) {
            try {
                const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
                const dependencies = {
                    ...(packageJson.dependencies || {}),
                    ...(packageJson.devDependencies || {}),
                };
                // Check for unused dependencies
                for (const [depName, _] of Object.entries(dependencies)) {
                    const isUsed = await this.isDependencyUsed(depName, files);
                    if (!isUsed) {
                        unused.push({
                            file: `package.json (${depName})`,
                            reason: "unused-dependency",
                            details: `Dependency '${depName}' is not used in the codebase`,
                        });
                    }
                }
            }
            catch (error) {
                // Skip if package.json can't be parsed
            }
        }
        return unused;
    }
    hashContent(content) {
        // Simple hash function for content comparison
        let hash = 0;
        for (let i = 0; i < content.length; i++) {
            const char = content.charCodeAt(i);
            hash = (hash << 5) - hash + char;
            hash = hash & hash; // Convert to 32-bit integer
        }
        return hash.toString();
    }
    extractImports(content) {
        const imports = [];
        const importRegex = /import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]/g;
        let match;
        while ((match = importRegex.exec(content)) !== null) {
            if (match[1])
                imports.push(match[1]);
        }
        return imports;
    }
    extractExports(content) {
        const exports = [];
        // Named exports
        const namedExportRegex = /export\s+(?:const|let|var|function|class|interface|type)\s+(\w+)/g;
        let match;
        while ((match = namedExportRegex.exec(content)) !== null) {
            if (match[1])
                exports.push(match[1]);
        }
        // Default exports
        const defaultExportRegex = /export\s+default\s+(\w+)/g;
        while ((match = defaultExportRegex.exec(content)) !== null) {
            if (match[1])
                exports.push(match[1]);
        }
        return exports;
    }
    isEntryPoint(filePath) {
        const entryPoints = [
            "index.ts",
            "index.js",
            "main.ts",
            "main.js",
            "app/page.tsx",
            "pages/index.tsx",
            "pages/_app.tsx",
            "pages/_document.tsx",
        ];
        if (entryPoints.some((entry) => filePath.includes(entry)))
            return true;
        // Next.js App Router special files
        const nextjsSpecialFiles = [
            "layout.tsx", "layout.js", "layout.jsx",
            "page.tsx", "page.js", "page.jsx",
            "loading.tsx", "loading.js", "loading.jsx",
            "error.tsx", "error.js", "error.jsx",
            "not-found.tsx", "not-found.js", "not-found.jsx",
            "route.ts", "route.js" // API Routes
        ];
        if (nextjsSpecialFiles.some((entry) => filePath.endsWith(entry)))
            return true;
        return false;
    }
    async isDependencyUsed(depName, files) {
        for (const file of files) {
            if (file.endsWith(".ts") ||
                file.endsWith(".tsx") ||
                file.endsWith(".js") ||
                file.endsWith(".jsx")) {
                try {
                    const content = await fs.readFile(file, "utf-8");
                    if (content.includes(depName)) {
                        return true;
                    }
                }
                catch (error) {
                    continue;
                }
            }
        }
        return false;
    }
    displayReport(report, verbose) {
        console.log(chalk_1.default.blue.bold("\n📊 Sanitization Report\n"));
        console.log(chalk_1.default.yellow("Summary:"));
        console.log(`  Total Issues: ${report.summary.totalIssues}`);
        console.log(`  Duplicates: ${report.summary.duplicates}`);
        console.log(`  Unreachable: ${report.summary.unreachable}`);
        console.log(`  Redundant: ${report.summary.redundant}`);
        console.log(`  Unused: ${report.summary.unused}\n`);
        if (report.duplicates.length > 0) {
            console.log(chalk_1.default.red.bold("🔍 Duplicate Files:"));
            for (const duplicate of report.duplicates) {
                console.log(chalk_1.default.red(`  ${duplicate.type.toUpperCase()} (${duplicate.similarity}% similar, ${duplicate.size} bytes)`));
                for (const file of duplicate.files) {
                    console.log(chalk_1.default.gray(`    - ${file}`));
                }
                console.log();
            }
        }
        if (report.unreachable.length > 0) {
            console.log(chalk_1.default.yellow.bold("🚫 Unreachable Files:"));
            for (const unreachable of report.unreachable) {
                console.log(chalk_1.default.yellow(`  ${unreachable.file}`));
                console.log(chalk_1.default.gray(`    Reason: ${unreachable.reason}`));
                console.log(chalk_1.default.gray(`    Details: ${unreachable.details}\n`));
            }
        }
        if (report.redundant.length > 0) {
            console.log(chalk_1.default.yellow.bold("🔄 Redundant Files:"));
            for (const redundant of report.redundant) {
                console.log(chalk_1.default.yellow(`  ${redundant.file}`));
                console.log(chalk_1.default.gray(`    Reason: ${redundant.reason}`));
                console.log(chalk_1.default.gray(`    Details: ${redundant.details}`));
                if (redundant.suggestion) {
                    console.log(chalk_1.default.cyan(`    Suggestion: ${redundant.suggestion}`));
                }
                console.log();
            }
        }
        if (report.unused.length > 0) {
            console.log(chalk_1.default.magenta.bold("🗑️ Unused Files:"));
            for (const unused of report.unused) {
                console.log(chalk_1.default.magenta(`  ${unused.file}`));
                console.log(chalk_1.default.gray(`    Reason: ${unused.reason}`));
                console.log(chalk_1.default.gray(`    Details: ${unused.details}\n`));
            }
        }
    }
    showFixSuggestions(report) {
        if (report.summary.totalIssues === 0) {
            console.log(chalk_1.default.green.bold("✅ No issues found! Your project is clean."));
            return;
        }
        console.log(chalk_1.default.blue.bold("\n🔧 Fix Suggestions:"));
        console.log(chalk_1.default.gray("Run with --fix flag to automatically fix issues:"));
        console.log(chalk_1.default.cyan("  mycontext sanitize --fix\n"));
        if (report.duplicates.length > 0) {
            console.log(chalk_1.default.yellow("For duplicate files:"));
            console.log(chalk_1.default.gray("  - Review and consolidate duplicate files"));
            console.log(chalk_1.default.gray("  - Remove unnecessary copies\n"));
        }
        if (report.unreachable.length > 0) {
            console.log(chalk_1.default.yellow("For unreachable files:"));
            console.log(chalk_1.default.gray("  - Remove unused files or add proper imports"));
            console.log(chalk_1.default.gray("  - Check if files should be entry points\n"));
        }
        if (report.redundant.length > 0) {
            console.log(chalk_1.default.yellow("For redundant files:"));
            console.log(chalk_1.default.gray("  - Rename or consolidate files with same names"));
            console.log(chalk_1.default.gray("  - Remove obsolete files\n"));
        }
        if (report.unused.length > 0) {
            console.log(chalk_1.default.yellow("For unused dependencies:"));
            console.log(chalk_1.default.gray("  - Remove unused dependencies from package.json"));
            console.log(chalk_1.default.gray("  - Run 'npm prune' or 'pnpm prune' to clean up\n"));
        }
    }
    async fixIssues(report, projectPath) {
        console.log(chalk_1.default.blue.bold("\n🔧 Fixing Issues...\n"));
        let fixedCount = 0;
        // Fix duplicate files (keep the first one, remove others)
        for (const duplicate of report.duplicates) {
            if (duplicate.files.length > 1) {
                const keepFile = duplicate.files[0];
                const removeFiles = duplicate.files.slice(1);
                for (const file of removeFiles) {
                    try {
                        await fs.remove(file);
                        console.log(chalk_1.default.green(`  ✅ Removed duplicate: ${path_1.default.relative(projectPath, file)}`));
                        fixedCount++;
                    }
                    catch (error) {
                        console.log(chalk_1.default.red(`  ❌ Failed to remove: ${path_1.default.relative(projectPath, file)}`));
                    }
                }
            }
        }
        // Fix unused dependencies
        for (const unused of report.unused) {
            if (unused.reason === "unused-dependency") {
                const depName = unused.file.match(/\(([^)]+)\)/)?.[1];
                if (depName) {
                    try {
                        // This would require modifying package.json
                        console.log(chalk_1.default.yellow(`  ⚠️ Manual fix needed: Remove dependency '${depName}' from package.json`));
                    }
                    catch (error) {
                        console.log(chalk_1.default.red(`  ❌ Failed to remove dependency: ${depName}`));
                    }
                }
            }
        }
        console.log(chalk_1.default.green.bold(`\n✅ Fixed ${fixedCount} issues`));
        console.log(chalk_1.default.gray("Some issues may require manual review and fixing."));
    }
}
exports.SanitizeCommand = SanitizeCommand;
//# sourceMappingURL=sanitize.js.map