"""
chunkhive CLI - Production-grade with clean organized output
"""
import typer
from typing import Optional, List
from pathlib import Path
import sys
import json
from datetime import datetime
from chunkhive.search_cli import search_app

try:
    from .embeddings_cli import embed_app
    EMBEDDINGS_AVAILABLE = True
except ImportError:
    EMBEDDINGS_AVAILABLE = False
    embed_app = None

app = typer.Typer(
    name="chunkhive",
    help="Hierarchical, semantic code chunking for AI systems",
    no_args_is_help=True,
)

if EMBEDDINGS_AVAILABLE and embed_app:
    app.add_typer(embed_app, name="embed")

app.add_typer(search_app, name="search")

# ---------------------------------------------------------------------
# SIMPLE BANNER
# ---------------------------------------------------------------------
def _print_banner():
    """Simple ASCII banner"""
    print("="*60)
    print("       chunkhive - Hierarchical Code Chunking")
    print("="*60)

# ---------------------------------------------------------------------
# ORGANIZED OUTPUT PATH HANDLING
# ---------------------------------------------------------------------
def _resolve_output_folder(
    user_output: Optional[str],
    base_name: str,
    source_type: str = "local",
    create_subfolder: bool = True,
) -> Path:
    """
    Create organized output folder structure.
    """
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    
    if create_subfolder:
        folder_name = f"{base_name}_{timestamp}"
    else:
        folder_name = base_name
    
    if user_output is None:
        if source_type == "local":
            base_dir = Path("data/processed/local_runs")
        else:
            base_dir = Path("data/processed/repo_runs")
        output_folder = base_dir / folder_name
        output_folder.mkdir(parents=True, exist_ok=True)
        return output_folder
    
    user_path = Path(user_output)
    
    if user_path.suffix == '.jsonl':
        parent_dir = user_path.parent
        if create_subfolder:
            output_folder = parent_dir / folder_name
            output_folder.mkdir(parents=True, exist_ok=True)
            return output_folder
        else:
            parent_dir.mkdir(parents=True, exist_ok=True)
            return parent_dir
    
    if user_path.exists() and user_path.is_dir():
        if create_subfolder:
            output_folder = user_path / folder_name
            output_folder.mkdir(parents=True, exist_ok=True)
            return output_folder
        else:
            return user_path
    
    if user_path.suffix == '' or '.' not in str(user_path):
        user_path.mkdir(parents=True, exist_ok=True)
        if create_subfolder:
            output_folder = user_path / folder_name
            output_folder.mkdir(parents=True, exist_ok=True)
            return output_folder
        else:
            return user_path
    
    parent_dir = user_path.parent
    parent_dir.mkdir(parents=True, exist_ok=True)
    if create_subfolder:
        output_folder = parent_dir / folder_name
        output_folder.mkdir(parents=True, exist_ok=True)
        return output_folder
    else:
        return parent_dir

# ---------------------------------------------------------------------
# BASIC COMMANDS
# ---------------------------------------------------------------------

@app.command()
def version():
    """Show chunkhive version."""
    try:
        from . import __version__
        print(f"chunkhive v{__version__}")
    except:
        print("chunkhive v0.3.1")

@app.command()
def info():
    """Show system information."""
    _print_banner()
    print(f"Python: {sys.version.split()[0]}")
    print("Features: Hierarchical chunking, AST + Tree-sitter, Git crawling")
    print("GitHub: https://github.com/AgentAhmed/ChunkHive")

# ---------------------------------------------------------------------
# CHUNK COMMANDS
# ---------------------------------------------------------------------
chunk_app = typer.Typer()
app.add_typer(chunk_app, name="chunk")

# 1. LOCAL FILE/DIRECTORY CHUNKING
@chunk_app.command("local")
def chunk_local(
    path: str = typer.Argument(..., help="Local directory or file path"),
    name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
    include: Optional[str] = typer.Option(None, "--include", help="Specific folder/file to include"),
    output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
    stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
    organized: bool = typer.Option(True, "--organized/--flat", 
                                   help="Organize output in timestamped folder (default: organized)"),
):
    """
    Chunk local files/directories.
    """
    _print_banner()
    
    try:
        from .chunkers.repo_parser import RepoChunker
        from .export.local_exporter import export_chunks_jsonl
        from .metadata.chunk_metadata import write_dataset_metadata
        
        path_obj = Path(path)
        
        if not path_obj.exists():
            print(f"❌ ERROR: Path not found: {path}")
            return
        
        if include:
            path_obj = path_obj / include
            if not path_obj.exists():
                print(f"❌ ERROR: Include path not found: {include}")
                return
        
        chunker = RepoChunker()
        all_chunks = []
        file_count = 0
        
        print(f"📁 Processing: {path_obj}")
        
        source_metadata = {
            "source_name": path_obj.name,  # ✅ Actual folder name
            "output_name": name or path_obj.name,  # ✅ User's name or default
            "source_type": "local_directory",
            "local_path": str(path_obj),
            "extensions_included": "ALL",
            "timestamp": datetime.now().strftime("%Y%m%d_%H%M%S"),
        }
        
        if path_obj.is_file():
            print(f"📄 Processing single file")
            chunks = chunker.chunk_file(path_obj, source_metadata)
            if chunks:
                all_chunks.extend(chunks)
                file_count = 1
        else:
            files_processed = 0
            files_list = list(path_obj.rglob("*"))
            total_files = len([f for f in files_list if f.is_file() and not any(part.startswith('.') for part in f.parts)])
            
            if total_files == 0:
                print(f"⚠️  No files found")
                return
            
            print(f"📊 Found {total_files} files to process...")
            
            for file_path in files_list:
                if file_path.is_file():
                    try:
                        if any(part.startswith('.') for part in file_path.parts):
                            continue
                        
                        chunks = chunker.chunk_file(file_path, source_metadata)
                        if chunks:
                            all_chunks.extend(chunks)
                            file_count += 1
                            files_processed += 1
                            
                            if files_processed % 50 == 0:
                                print(f"  ✅ Processed {files_processed}/{total_files} files...")
                                
                    except Exception as e:
                        print(f"  ⚠️  Skipping {file_path.name}: {str(e)[:50]}")
        
        if not all_chunks:
            print("⚠️  WARNING: No chunks generated")
            return
        
        # Resolve output folder
        base_output_name = name or path_obj.name
        output_folder = _resolve_output_folder(
            user_output=output,
            base_name=base_output_name,
            source_type="local",
            create_subfolder=organized,
        )
        
        # Create file paths
        if organized:
            chunks_file = output_folder / "chunks.jsonl"
            stats_file = output_folder / "stats.json"
            metadata_file = output_folder / "metadata.json"
        else:
            chunks_file = output_folder / f"{base_output_name}.jsonl"
            stats_file = output_folder / f"{base_output_name}_stats.json"
            metadata_file = output_folder / f"{base_output_name}_metadata.json"

        # Handle stats export
        if stats:
            enhanced_stats = export_chunks_jsonl(
                chunks=all_chunks,
                output_path=chunks_file,
                print_stats=True,
                source_name=path_obj.name,
                source_type="local_directory",
            )
            
            if enhanced_stats:
                with open(stats_file, 'w') as f:
                    json.dump(enhanced_stats, f, indent=2)
            else:
                from .metadata.chunk_stats import compute_dataset_stats
                basic_stats = compute_dataset_stats(all_chunks)
                enhanced_stats = {
                    "source_name": path_obj.name,
                    "source_type": "local_directory",
                    "processing_timestamp": datetime.now().isoformat(),
                    **basic_stats
                }
                with open(stats_file, 'w') as f:
                    json.dump(enhanced_stats, f, indent=2)
        else:
            # ✅ FIXED: Capture and save stats even when --stats not used
            enhanced_stats = export_chunks_jsonl(
                chunks=all_chunks,
                output_path=chunks_file,
                print_stats=False,  # Don't print to console
                source_name=path_obj.name,
                source_type="local_directory",
            )
            
            # ✅ Save stats.json (with fallback safety)
            if enhanced_stats:
                with open(stats_file, 'w') as f:
                    json.dump(enhanced_stats, f, indent=2)
            else:
                # Fallback (should not happen with updated exporter)
                from .metadata.chunk_stats import compute_dataset_stats
                basic_stats = compute_dataset_stats(all_chunks)
                enhanced_stats = {
                    "source_name": path_obj.name,
                    "source_type": "local_directory",
                    "processing_timestamp": datetime.now().isoformat(),
                    **basic_stats
                }
                with open(stats_file, 'w') as f:
                    json.dump(enhanced_stats, f, indent=2)    
        
        # # Handle stats export
        # if stats:
        #     enhanced_stats = export_chunks_jsonl(
        #         chunks=all_chunks,
        #         output_path=chunks_file,
        #         print_stats=True,
        #         source_name=path_obj.name,
        #         source_type="local_directory",
        #     )
            
        #     if enhanced_stats:
        #         with open(stats_file, 'w') as f:
        #             json.dump(enhanced_stats, f, indent=2)
        #     else:
        #         from .metadata.chunk_stats import compute_dataset_stats
        #         basic_stats = compute_dataset_stats(all_chunks)
        #         enhanced_stats = {
        #             "source_name": path_obj.name,
        #             "source_type": "local_directory",
        #             "processing_timestamp": datetime.now().isoformat(),
        #             **basic_stats
        #         }
        #         with open(stats_file, 'w') as f:
        #             json.dump(enhanced_stats, f, indent=2)
        # else:
        #     export_chunks_jsonl(
        #         chunks=all_chunks,
        #         output_path=chunks_file,
        #         print_stats=False,
        #         source_name=path_obj.name,
        #         source_type="local_directory",
        #     )
        
        # Save metadata
        write_dataset_metadata(
            chunks=all_chunks,
            output_path=metadata_file,
            dataset_name=name or path_obj.name,
            dataset_version="v1",
        )
        
        # ✅ CLEAN SUCCESS MESSAGE - NO DUPLICATION
        print(f"\n" + "="*60)
        print("✅ SUCCESS: Local Processing Complete")
        print("="*60)
        print(f"📊 Source: {path_obj.name}")
        print(f"📄 Files: {file_count}")
        print(f"🧩 Chunks: {len(all_chunks)}")
        print(f"📂 Output folder: {output_folder}")
        
    except PermissionError as e:
        print(f"❌ PERMISSION ERROR: {e}")
    except Exception as e:
        print(f"❌ ERROR: {e}")
        import traceback
        traceback.print_exc()

# 2. GIT REPOSITORY CHUNKING
@chunk_app.command("repo")
def chunk_repo_cmd(
    repo_url: str = typer.Argument(..., help="Git repository URL"),
    name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
    extensions: Optional[str] = typer.Option(
        None, "--extensions", 
        help="File extensions to include (comma-separated, e.g., .py,.md)"
    ),
    max_files: Optional[int] = typer.Option(
        None, "--max-files", 
        help="Maximum number of files to process"
    ),
    output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
    stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
):
    """
    Chunk a Git repository.
    """
    _print_banner()
    
    try:
        from .ingestion.repo_crawler import GitCrawler
        from .metadata.repo_metadata import RepoMetadataExtractor
        from .chunkers.repo_parser import RepoChunker
        from .export.repo_exporter import export_repo_chunks_jsonl
        from .metadata.chunk_metadata import write_dataset_metadata
        
        print(f"🌐 Processing: {repo_url}")
        
        crawler = GitCrawler()
        repo_path = crawler.clone_repository(repo_url)
        if not repo_path:
            print(f"❌ ERROR: Failed to clone repository")
            return
        
        extractor = RepoMetadataExtractor(repo_path)
        repo_metadata = extractor.extract_comprehensive_metadata()
        
        repo_metadata["source_name"] = repo_path.name  # ✅ ALWAYS actual repo name
        repo_metadata["output_name"] = name or repo_path.name  # ✅ Custom or default output name
        repo_metadata["source_type"] = "git_repository"
        repo_metadata["repo_url"] = repo_url
        repo_metadata["local_path"] = str(repo_path)
        repo_metadata["extensions_included"] = extensions if extensions else "ALL"
        repo_metadata["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        ext_set = None
        if extensions:
            ext_set = set(ext.strip() for ext in extensions.split(","))
        
        file_infos, file_stats = crawler.list_files_with_info(
            repo_path,
            extensions=ext_set,
            exclude_dirs={'.git', '__pycache__', 'node_modules', 'build', 'dist', '.venv'},
            skip_binary=True
        )
        
        if max_files and max_files > 0:
            file_infos = file_infos[:max_files]
            print(f"📊 Limiting to {max_files} files")
        
        base_output_name = name or repo_path.name
        output_folder = _resolve_output_folder(
            user_output=output,
            base_name=base_output_name,
            source_type="repo",
            create_subfolder=True,
        )
        
        chunks_file = output_folder / "chunks.jsonl"
        metadata_file = output_folder / "metadata.json"
        
        all_chunks = []
        chunker = RepoChunker()
        processed_files = 0
        total_files = len(file_infos)
        
        print(f"📊 Processing {total_files} files...")
        
        for idx, file_info in enumerate(file_infos, 1):
            try:
                file_specific_metadata = {
                    **repo_metadata,
                    "file_info": {
                        "relative_path": file_info.relative_path,
                        "size_bytes": file_info.size,
                        "extension": file_info.extension,
                        "is_binary": file_info.is_binary,
                        "absolute_path": str(file_info.path),
                    }
                }
                
                chunks = chunker.chunk_file(file_info.path, file_specific_metadata)
                if chunks:
                    all_chunks.extend(chunks)
                    processed_files += 1
                    
                    if idx % 50 == 0 or idx == total_files:
                        print(f"  ✅ Processed {idx}/{total_files} files ({len(all_chunks)} chunks)")
                        
            except Exception as e:
                print(f"  ⚠️  Skipping {file_info.path.name}: {str(e)[:80]}")
        
        if not all_chunks:
            print("⚠️  WARNING: No chunks generated")
            return
        
        final_metadata = {
            **repo_metadata,
            "processing_stats": {
                "total_files_listed": total_files,
                "processed_files": processed_files,
                "total_chunks": len(all_chunks),
                "extensions_filter": extensions,
                "max_files_limit": max_files,
            }
        }
        
        export_repo_chunks_jsonl(
            chunks=all_chunks,
            output_path=chunks_file,
            repo_metadata=final_metadata,
            print_stats=stats,
        )
        
        write_dataset_metadata(
            chunks=all_chunks,
            output_path=metadata_file,
            dataset_name=name or repo_path.name,
            dataset_version="v1",
        )
        
        # ✅ CLEAN SUCCESS MESSAGE - NO DUPLICATION
        print(f"\n" + "="*60)
        print("✅ SUCCESS: Repository Processing Complete")
        print("="*60)
        print(f"📊 Source: {repo_path.name}")
        print(f"📄 Files: {processed_files}")
        print(f"🧩 Chunks: {len(all_chunks)}")
        print(f"📂 Output folder: {output_folder}")
        
        agentic = repo_metadata.get("agentic_detection", {})
        if agentic:
            frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
            if frameworks:
                print(f"🤖 Agentic frameworks: {', '.join(frameworks)}")
        
    except PermissionError as e:
        print(f"❌ PERMISSION ERROR: {e}")
    except Exception as e:
        print(f"❌ ERROR: {e}")
        import traceback
        traceback.print_exc()

# 3. SINGLE FILE CHUNKING
@chunk_app.command("file")
def chunk_file_cmd(
    file_path: str = typer.Argument(..., help="File to chunk"),
    output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
    stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
    organized: bool = typer.Option(True, "--organized/--flat", 
                                   help="Organize output in timestamped folder (default: organized)"),
):
    """Chunk a single file."""
    chunk_local(path=file_path, output=output, stats=stats, organized=organized)

# 4. SINGLE REPOSITORY (alias)
@chunk_app.command("single")
def chunk_single_cmd(
    repo_url: str = typer.Argument(..., help="Git repository URL"),
    name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
    extensions: Optional[str] = typer.Option(
        None, "--extensions", 
        help="File extensions to include (comma-separated)"
    ),
    max_files: Optional[int] = typer.Option(
        None, "--max-files", 
        help="Maximum number of files to process"
    ),
    output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
    stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
):
    """Chunk a single Git repository."""
    return chunk_repo_cmd(
        repo_url=repo_url,
        name=name,
        extensions=extensions,
        max_files=max_files,
        output=output,
        stats=stats
    )

# ---------------------------------------------------------------------
# ANALYZE COMMAND
# ---------------------------------------------------------------------

@app.command("analyze")
def analyze_cmd(
    repo_path: str = typer.Argument(..., help="Repository path or URL"),
    output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
    full: bool = typer.Option(False, "--full", help="Show full details"),
):
    """Analyze repository metadata."""
    _print_banner()
    
    try:
        from .metadata.repo_metadata import RepoMetadataExtractor
        from pathlib import Path
        
        if repo_path.startswith(("http://", "https://", "git@")):
            from .ingestion.repo_crawler import GitCrawler
            crawler = GitCrawler()
            repo_obj = crawler.clone_repository(repo_path)
            if not repo_obj:
                print(f"❌ ERROR: Failed to clone repository")
                return
        else:
            repo_obj = Path(repo_path)
            if not repo_obj.exists():
                print(f"❌ ERROR: Path not found")
                return
        
        print(f"🔍 Analyzing: {repo_obj}")
        extractor = RepoMetadataExtractor(repo_obj)
        analysis = extractor.extract_comprehensive_metadata()
        
        analysis["source_url"] = repo_path if repo_path.startswith(("http://", "https://")) else f"file://{repo_obj.absolute()}"
        analysis["source_type"] = "git_repository" if repo_path.startswith(("http://", "https://", "git@")) else "local_directory"
        analysis["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        if output:
            output_path = Path(output)
            output_path.parent.mkdir(parents=True, exist_ok=True)
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(analysis, f, indent=2, default=str)
            print(f"✅ Analysis saved to {output_path}")
        else:
            print("📊 Analysis Results:")
            basic = analysis.get("basic", {})
            print(f"  📁 Name: {basic.get('repo_name', 'N/A')}")
            print(f"  📄 Files: {basic.get('file_count', 0):,}")
            print(f"  💾 Size: {basic.get('size_mb', 0)} MB")
            
            agentic = analysis.get("agentic_detection", {})
            if agentic:
                frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
                if frameworks:
                    print(f"  🤖 Agentic Frameworks: {', '.join(frameworks)}")
            
            if full:
                print("\n📋 Full Details:")
                print(json.dumps(analysis, indent=2, default=str))
        
    except Exception as e:
        print(f"❌ ERROR: {e}")

# ---------------------------------------------------------------------
# BATCH PROCESSING
# ---------------------------------------------------------------------

@app.command("batch")
def batch_cmd(
    config_file: str = typer.Argument(..., help="JSON config file with repositories list"),
    output_dir: Optional[str] = typer.Option(None, "-o", "--output-dir", help="Output directory"),
):
    """Batch process multiple repositories."""
    _print_banner()
    
    try:
        config_path = Path(config_file)
        if not config_path.exists():
            print(f"❌ ERROR: Config file not found")
            return
        
        with open(config_path, 'r') as f:
            config = json.load(f)
        
        repos = config.get("repositories", [])
        if not repos:
            print("❌ ERROR: No repositories found")
            return
        
        if output_dir:
            output_base = Path(output_dir)
        else:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            output_base = Path(f"batch_output_{timestamp}")
        
        output_base.mkdir(parents=True, exist_ok=True)
        
        print(f"📁 Processing {len(repos)} repositories...")
        print(f"📂 Output: {output_base}")
        print("-"*60)
        
        results = []
        for i, repo_config in enumerate(repos, 1):
            repo_url = repo_config.get("url")
            repo_name = repo_config.get("name")
            extensions = repo_config.get("extensions")
            max_files = repo_config.get("max_files")
            
            if not repo_url:
                print(f"❌ Skipping item {i}: No URL")
                continue
            
            print(f"\n[{i}/{len(repos)}] Processing: {repo_url}")
            
            try:
                chunk_repo_cmd(
                    repo_url=repo_url,
                    name=repo_name,
                    extensions=extensions,
                    max_files=max_files,
                    output=str(output_base / f"{repo_name or 'repo'}_{i:03d}"),
                    stats=False,
                )
                results.append({"repo": repo_url, "status": "success"})
            except Exception as e:
                print(f"❌ Failed: {str(e)[:100]}")
                results.append({"repo": repo_url, "status": "failed", "error": str(e)})
        
        results_file = output_base / "batch_results.json"
        with open(results_file, 'w') as f:
            json.dump({
                "total_repos": len(repos),
                "successful": len([r for r in results if r["status"] == "success"]),
                "failed": len([r for r in results if r["status"] == "failed"]),
                "results": results,
                "timestamp": datetime.now().isoformat(),
            }, f, indent=2)
        
        print(f"\n✅ BATCH PROCESSING COMPLETE")
        print(f"📊 Results: {results_file}")
        
    except Exception as e:
        print(f"❌ ERROR: {e}")

# ---------------------------------------------------------------------
# HELP
# ---------------------------------------------------------------------

@app.command("examples")
def show_examples():
    """Show usage examples."""
    _print_banner()
    print("\n📚 USAGE EXAMPLES:")
    print("="*60)
    
    print("\n📁 LOCAL FILES:")
    print("  chunkhive chunk local ./project")
    print("  chunkhive chunk local ./project --name my_data")
    print("  chunkhive chunk local ./project --flat")
    print("  chunkhive chunk file example.py --stats")
    
    print("\n🌐 GIT REPOSITORIES:")
    print("  chunkhive chunk repo https://github.com/user/repo")
    print("  chunkhive chunk repo https://github.com/user/repo --extensions .py,.md")
    print("  chunkhive chunk repo https://github.com/user/repo --max-files 100")
    
    print("\n🔍 ANALYSIS:")
    print("  chunkhive analyze https://github.com/user/repo")
    
    print("\n🔄 BATCH:")
    print("  chunkhive batch repos.json")
    
    print("\nℹ️  INFO:")
    print("  chunkhive version")
    print("  chunkhive --help")
    print("="*60)

# ---------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------

if __name__ == "__main__":
    app()

# """
# chunkhive CLI - Production-grade with complete metadata - FIXED VERSION
# """
# import typer
# from typing import Optional, List
# from pathlib import Path
# import sys
# import json
# from datetime import datetime  # ✅ ADDED for enhanced stats

# app = typer.Typer(
#     name="chunkhive",
#     help="Hierarchical, semantic code chunking for AI systems",
#     no_args_is_help=True,
# )

# # ---------------------------------------------------------------------
# # SIMPLE BANNER
# # ---------------------------------------------------------------------
# def _print_banner():
#     """Simple ASCII banner"""
#     print("="*60)
#     print("       chunkhive - Hierarchical Code Chunking")
#     print("="*60)

# # ---------------------------------------------------------------------
# # OUTPUT PATH HANDLING - FIXED
# # ---------------------------------------------------------------------
# def _resolve_output_path(
#     user_output: Optional[str],
#     default_name: str,
#     is_directory_input: bool = False
# ) -> Path:
#     """
#     Intelligently resolve output path based on user input.
    
#     Rules:
#     1. If user_output is None → Use default_name in current directory
#     2. If user_output ends with .jsonl → Treat as file path
#     3. If user_output is existing directory → Put default_name in that directory
#     4. Otherwise → Try to create parent directories for file path
    
#     Args:
#         user_output: User-provided output path (or None)
#         default_name: Default filename if not specified
#         is_directory_input: Whether input is a directory (for better defaults)
#     """
#     if user_output is None:
#         # No output specified, use default in current directory
#         return Path.cwd() / default_name
    
#     user_path = Path(user_output)
    
#     # Check if it looks like a file (has .jsonl extension or no extension but not an existing dir)
#     if user_path.suffix == '.jsonl':
#         # User specified a file with .jsonl extension
#         user_path.parent.mkdir(parents=True, exist_ok=True)
#         return user_path
    
#     # Check if it's an existing directory
#     if user_path.exists() and user_path.is_dir():
#         # User specified a directory, put default file inside it
#         return user_path / default_name
    
#     # Check if parent directory exists or can be created
#     if user_path.suffix == '':
#         # No extension, might be intended as directory
#         user_path.mkdir(parents=True, exist_ok=True)
#         return user_path / default_name
#     else:
#         # Has some extension, treat as file
#         user_path.parent.mkdir(parents=True, exist_ok=True)
#         return user_path

# # ---------------------------------------------------------------------
# # BASIC COMMANDS
# # ---------------------------------------------------------------------

# @app.command()
# def version():
#     """Show chunkhive version."""
#     try:
#         from . import __version__
#         print(f"chunkhive v{__version__}")
#     except:
#         print("chunkhive v0.1.7")  # ✅ UPDATED to 0.1.7

# @app.command()
# def info():
#     """Show system information."""
#     _print_banner()
#     print(f"Python: {sys.version.split()[0]}")
#     print("Available: Python, Markdown, JSON, YAML, TXT, MDX files")
#     print("GitHub: https://github.com/AgentAhmed/ChunkHive")

# # ---------------------------------------------------------------------
# # CHUNK COMMANDS - FIXED TO MATCH PIPELINES
# # ---------------------------------------------------------------------
# chunk_app = typer.Typer()
# app.add_typer(chunk_app, name="chunk")

# # 1. LOCAL FILE/DIRECTORY CHUNKING - FIXED to match local_pipeline.py
# @chunk_app.command("local")
# def chunk_local(
#     path: str = typer.Argument(..., help="Local directory or file path"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     include: Optional[str] = typer.Option(None, "--include", help="Specific folder/file to include"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk local files/directories (matches local_pipeline.py pattern).
    
#     Examples:
#         chunkhive chunk local ./project              # Creates ./project.jsonl
#         chunkhive chunk local ./project -o ./output  # Creates ./output/project.jsonl
#         chunkhive chunk local ./project -o ./output/myproject.jsonl  # Creates specific file
#         chunkhive chunk local ./project --name my_data  # Creates ./my_data.jsonl
#     """
#     _print_banner()
    
#     try:
#         from .chunkers.repo_parser import RepoChunker
#         from .export.local_exporter import export_chunks_jsonl
#         from .metadata.chunk_stats import compute_dataset_stats
#         from .metadata.chunk_metadata import write_dataset_metadata
        
#         path_obj = Path(path)
        
#         if not path_obj.exists():
#             print(f"❌ ERROR: Path not found: {path}")
#             return
        
#         # Handle include parameter
#         if include:
#             path_obj = path_obj / include
#             if not path_obj.exists():
#                 print(f"❌ ERROR: Include path not found: {include}")
#                 return
        
#         chunker = RepoChunker()
#         all_chunks = []
#         file_count = 0
        
#         print(f"📁 Processing: {path_obj}")
        
#         # Create proper source metadata (not repo_metadata)
#         source_metadata = {
#             "source_name": name or path_obj.name,
#             "source_type": "local_directory",
#             "local_path": str(path_obj),
#             "extensions_included": "ALL",
#             "timestamp": datetime.now().strftime("%Y%m%d_%H%M%S"),
#         }
        
#         if path_obj.is_file():
#             # Single file processing
#             print(f"📄 Processing single file: {path_obj.name}")
            
#             chunks = chunker.chunk_file(path_obj, source_metadata)
#             if chunks:
#                 all_chunks.extend(chunks)
#                 file_count = 1
#         else:
#             # Directory processing
#             files_processed = 0
#             files_list = list(path_obj.rglob("*"))
#             total_files = len([f for f in files_list if f.is_file() and not any(part.startswith('.') for part in f.parts)])
            
#             print(f"📊 Found {total_files} files to process...")
            
#             for file_path in files_list:
#                 if file_path.is_file():
#                     try:
#                         # Skip hidden/system files
#                         if any(part.startswith('.') for part in file_path.parts):
#                             continue
                        
#                         chunks = chunker.chunk_file(file_path, source_metadata)
#                         if chunks:
#                             all_chunks.extend(chunks)
#                             file_count += 1
#                             files_processed += 1
                            
#                             if files_processed % 50 == 0:
#                                 print(f"  ✅ Processed {files_processed}/{total_files} files...")
                                
#                     except Exception as e:
#                         print(f"  ⚠️  Skipping {file_path.name}: {str(e)[:50]}")
        
#         # ✅ FIXED: Resolve output path intelligently
#         if name:
#             default_filename = f"{name}.jsonl"
#         elif path_obj.is_file():
#             default_filename = f"chunks_{path_obj.stem}.jsonl"
#         else:
#             default_filename = f"chunks_{path_obj.name}.jsonl"
        
#         output_path = _resolve_output_path(
#             user_output=output,
#             default_name=default_filename,
#             is_directory_input=path_obj.is_dir()
#         )
        
#         print(f"📁 Output will be saved to: {output_path}")
        
#         if all_chunks:
#             # ✅ FIXED: Ensure parent directory exists
#             output_path.parent.mkdir(parents=True, exist_ok=True)
            
#             # ✅ FIXED: Use enhanced local_exporter with source info
#             stats_result = export_chunks_jsonl(  # ✅ CHANGED: Capture return value
#                 chunks=all_chunks,
#                 output_path=output_path,
#                 print_stats=stats,
#                 source_name=path_obj.name,  # ✅ ADDED: Pass actual source name
#                 source_type="local_directory",
#             )
            
#             # ✅ FIXED: Create enhanced stats file
#             if stats_result:
#                 stats_file = output_path.parent / f"{output_path.stem}_stats.json"
#                 with open(stats_file, 'w') as f:
#                     json.dump(stats_result, f, indent=2)
#             else:
#                 # Fallback to original stats if needed
#                 stats_data = compute_dataset_stats(all_chunks)
#                 stats_file = output_path.parent / f"{output_path.stem}_stats.json"
#                 with open(stats_file, 'w') as f:
#                     json.dump(stats_data, f, indent=2)
            
#             # ✅ FIXED: Create metadata file (matches pipeline)
#             metadata_file = output_path.parent / f"{output_path.stem}_metadata.json"
#             write_dataset_metadata(
#                 chunks=all_chunks,
#                 output_path=metadata_file,
#                 dataset_name=name or path_obj.name,
#                 dataset_version="v1",
#             )
            
#             print(f"\n" + "="*60)
#             print("✅ SUCCESS: Local Processing Complete")
#             print("="*60)
#             print(f"📊 Source: {path_obj.name} (Local folder)")  # ✅ FIXED: Show actual source, not output name
#             print(f"📄 Files processed: {file_count}")
#             print(f"🧩 Total chunks generated: {len(all_chunks)}")
#             print(f"💾 Output files:")
#             print(f"   - Chunks: {output_path}")
#             print(f"   - Statistics: {stats_file}")
#             print(f"   - Metadata: {metadata_file}")
            
#         else:
#             print("⚠️  WARNING: No chunks generated")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#         print("💡 Try running with admin privileges or choose a different output location.")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")
#         import traceback
#         traceback.print_exc()

# # 2. GIT REPOSITORY CHUNKING - FIXED to match repo_pipeline.py
# @chunk_app.command("repo")
# def chunk_repo_cmd(
#     repo_url: str = typer.Argument(..., help="Git repository URL"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     extensions: Optional[str] = typer.Option(
#         None, "--extensions", 
#         help="File extensions to include (comma-separated, e.g., .py,.md)"
#     ),
#     max_files: Optional[int] = typer.Option(
#         None, "--max-files", 
#         help="Maximum number of files to process"
#     ),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a Git repository (matches repo_pipeline.py pattern).
    
#     Examples:
#         chunkhive chunk repo https://github.com/user/repo
#         chunkhive chunk repo https://github.com/user/repo -o ./output
#         chunkhive chunk repo https://github.com/user/repo -o ./my_repo.jsonl
#         chunkhive chunk repo https://github.com/user/repo --name langchain --extensions .py,.md
#     """
#     _print_banner()
    
#     try:
#         from .ingestion.repo_crawler import GitCrawler
#         from .metadata.repo_metadata import RepoMetadataExtractor
#         from .chunkers.repo_parser import RepoChunker
#         from .export.repo_exporter import export_repo_chunks_jsonl
#         from .metadata.chunk_metadata import write_dataset_metadata
        
#         print(f"🌐 Processing repository: {repo_url}")
        
#         # Initialize crawler
#         crawler = GitCrawler()
        
#         # Clone repository
#         repo_path = crawler.clone_repository(repo_url)
#         if not repo_path:
#             print(f"❌ ERROR: Failed to clone repository: {repo_url}")
#             return
        
#         # Extract repository metadata
#         extractor = RepoMetadataExtractor(repo_path)
#         repo_metadata = extractor.extract_comprehensive_metadata()
        
#         # ✅ FIXED: Add proper source naming
#         repo_metadata["source_name"] = name or repo_path.name
#         repo_metadata["source_type"] = "git_repository"
#         repo_metadata["repo_url"] = repo_url
#         repo_metadata["local_path"] = str(repo_path)
#         repo_metadata["extensions_included"] = extensions if extensions else "ALL"
#         repo_metadata["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
#         # Parse extensions
#         ext_set = None
#         if extensions:
#             ext_set = set(ext.strip() for ext in extensions.split(","))
        
#         # List files with info
#         file_infos, file_stats = crawler.list_files_with_info(
#             repo_path,
#             extensions=ext_set,
#             exclude_dirs={'.git', '__pycache__', 'node_modules', 'build', 'dist', '.venv'},
#             skip_binary=True
#         )
        
#         # Apply max files limit
#         if max_files and max_files > 0:
#             file_infos = file_infos[:max_files]
#             print(f"📊 Limiting to {max_files} files (out of {len(file_infos)})")
        
#         # ✅ FIXED: Resolve output path BEFORE processing
#         if name:
#             default_filename = f"{name}.jsonl"
#         else:
#             repo_name = repo_path.name
#             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
#             default_filename = f"chunks_{repo_name}_{timestamp}.jsonl"
        
#         output_path = _resolve_output_path(
#             user_output=output,
#             default_name=default_filename,
#             is_directory_input=True
#         )
        
#         print(f"📁 Output will be saved to: {output_path}")
        
#         # Process files with complete metadata
#         all_chunks = []
#         chunker = RepoChunker()
#         processed_files = 0
#         total_files = len(file_infos)
        
#         print(f"📊 Processing {total_files} files...")
        
#         for idx, file_info in enumerate(file_infos, 1):
#             try:
#                 # Create file-specific metadata
#                 file_specific_metadata = {
#                     **repo_metadata,
#                     "file_info": {
#                         "relative_path": file_info.relative_path,
#                         "size_bytes": file_info.size,
#                         "extension": file_info.extension,
#                         "is_binary": file_info.is_binary,
#                         "absolute_path": str(file_info.path),
#                     }
#                 }
                
#                 chunks = chunker.chunk_file(file_info.path, file_specific_metadata)
#                 if chunks:
#                     all_chunks.extend(chunks)
#                     processed_files += 1
                    
#                     # Progress indicator
#                     if idx % 50 == 0 or idx == total_files:
#                         print(f"  ✅ Processed {idx}/{total_files} files ({len(all_chunks)} chunks)")
                        
#             except Exception as e:
#                 print(f"  ⚠️  Skipping {file_info.path.name}: {str(e)[:80]}")
        
#         # Export with complete metadata
#         if all_chunks:
#             # ✅ FIXED: Ensure parent directory exists
#             output_path.parent.mkdir(parents=True, exist_ok=True)
            
#             # Add processing stats to metadata
#             final_metadata = {
#                 **repo_metadata,
#                 "processing_stats": {
#                     "total_files_listed": total_files,
#                     "processed_files": processed_files,
#                     "total_chunks": len(all_chunks),
#                     "extensions_filter": extensions,
#                     "max_files_limit": max_files,
#                 }
#             }
            
#             # ✅ FIXED: Use repo_exporter (matches repo_pipeline.py)
#             result = export_repo_chunks_jsonl(
#                 chunks=all_chunks,
#                 output_path=output_path,
#                 repo_metadata=final_metadata,
#                 print_stats=stats,
#             )
            
#             # ✅ FIXED: Create metadata file (matches pipeline)
#             metadata_file = output_path.parent / f"{output_path.stem}_metadata.json"
#             write_dataset_metadata(
#                 chunks=all_chunks,
#                 output_path=metadata_file,
#                 dataset_name=name or repo_path.name,
#                 dataset_version="v1",
#             )
            
#             print(f"\n" + "="*60)
#             print("✅ SUCCESS: Repository Processing Complete")
#             print("="*60)
#             print(f"📊 Source: {repo_path.name} (Git repository)")  # ✅ FIXED: Show actual repo name, not output name
#             print(f"📄 Files processed: {processed_files}")
#             print(f"🧩 Total chunks generated: {len(all_chunks)}")
#             print(f"💾 Output files:")
#             print(f"   - Chunks: {output_path}")
#             print(f"   - Statistics: {result.get('stats_file', 'N/A')}")
#             print(f"   - Metadata: {metadata_file}")
            
#             # Show agentic frameworks if detected
#             agentic = repo_metadata.get("agentic_detection", {})
#             if agentic:
#                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
#                 if frameworks:
#                     print(f"🤖 Agentic frameworks: {', '.join(frameworks)}")
                    
#         else:
#             print("⚠️  WARNING: No chunks generated")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#         print("💡 Try running with admin privileges or choose a different output location.")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")
#         import traceback
#         traceback.print_exc()

# # 3. SINGLE FILE CHUNKING (alias) - FIXED
# @chunk_app.command("file")
# def chunk_file_cmd(
#     file_path: str = typer.Argument(..., help="File to chunk"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a single file with complete metadata.
    
#     Examples:
#         chunkhive chunk file example.py
#         chunkhive chunk file example.py -o ./output
#         chunkhive chunk file example.py -o ./my_chunks.jsonl
#         chunkhive chunk file example.py --stats
#     """
#     chunk_local(path=file_path, output=output, stats=stats)

# # 4. SINGLE REPOSITORY (alias for backward compatibility)
# @chunk_app.command("single")
# def chunk_single_cmd(
#     repo_url: str = typer.Argument(..., help="Git repository URL"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     extensions: Optional[str] = typer.Option(
#         None, "--extensions", 
#         help="File extensions to include (comma-separated)"
#     ),
#     max_files: Optional[int] = typer.Option(
#         None, "--max-files", 
#         help="Maximum number of files to process"
#     ),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a single Git repository (alias for 'repo' command).
#     """
#     return chunk_repo_cmd(
#         repo_url=repo_url,
#         name=name,
#         extensions=extensions,
#         max_files=max_files,
#         output=output,
#         stats=stats
#     )

# # ---------------------------------------------------------------------
# # ANALYZE COMMAND (ENHANCED) - FIXED OUTPUT
# # ---------------------------------------------------------------------

# @app.command("analyze")
# def analyze_cmd(
#     repo_path: str = typer.Argument(..., help="Repository path or URL"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
#     full: bool = typer.Option(False, "--full", help="Show full details"),
# ):
#     """
#     Analyze repository metadata with complete output.
    
#     Examples:
#         chunkhive analyze https://github.com/user/repo
#         chunkhive analyze ./local/repo --output analysis.json
#         chunkhive analyze https://github.com/user/repo --full
#     """
#     _print_banner()
    
#     try:
#         from .metadata.repo_metadata import RepoMetadataExtractor
#         from pathlib import Path
        
#         # Check if it's a URL or local path
#         if repo_path.startswith(("http://", "https://", "git@")):
#             from .ingestion.repo_crawler import GitCrawler
#             crawler = GitCrawler()
#             repo_obj = crawler.clone_repository(repo_path)
#             if not repo_obj:
#                 print(f"❌ ERROR: Failed to clone repository: {repo_path}")
#                 return
#         else:
#             repo_obj = Path(repo_path)
#             if not repo_obj.exists():
#                 print(f"❌ ERROR: Path not found: {repo_path}")
#                 return
        
#         print(f"🔍 Analyzing: {repo_obj}")
#         extractor = RepoMetadataExtractor(repo_obj)
#         analysis = extractor.extract_comprehensive_metadata()
        
#         # Add missing fields for consistency
#         analysis["source_url"] = repo_path if repo_path.startswith(("http://", "https://")) else f"file://{repo_obj.absolute()}"
#         analysis["source_type"] = "git_repository" if repo_path.startswith(("http://", "https://", "git@")) else "local_directory"
#         analysis["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
#         if output:
#             output_path = Path(output)
#             output_path.parent.mkdir(parents=True, exist_ok=True)
#             with open(output_path, 'w', encoding='utf-8') as f:
#                 json.dump(analysis, f, indent=2, default=str)
#             print(f"✅ SUCCESS: Analysis saved to {output_path}")
#         else:
#             print("📊 Analysis Results:")
#             basic = analysis.get("basic", {})
#             print(f"  📁 Name: {basic.get('repo_name', 'N/A')}")
#             print(f"  📄 Files: {basic.get('file_count', 0):,}")
#             print(f"  💾 Size: {basic.get('size_mb', 0)} MB")
            
#             agentic = analysis.get("agentic_detection", {})
#             if agentic:
#                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
#                 if frameworks:
#                     print(f"  🤖 Agentic Frameworks: {', '.join(frameworks)}")
            
#             if full:
#                 print("\n📋 Full Details:")
#                 print(json.dumps(analysis, indent=2, default=str))
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")

# # ---------------------------------------------------------------------
# # BATCH PROCESSING COMMAND - FIXED OUTPUT
# # ---------------------------------------------------------------------

# @app.command("batch")
# def batch_cmd(
#     config_file: str = typer.Argument(..., help="JSON config file with repositories list"),
#     output_dir: Optional[str] = typer.Option(None, "-o", "--output-dir", help="Output directory"),
# ):
#     """Batch process multiple repositories from config file."""
#     _print_banner()
    
#     try:
#         config_path = Path(config_file)
#         if not config_path.exists():
#             print(f"❌ ERROR: Config file not found: {config_file}")
#             return
        
#         with open(config_path, 'r') as f:
#             config = json.load(f)
        
#         repos = config.get("repositories", [])
#         if not repos:
#             print("❌ ERROR: No repositories found in config file")
#             return
        
#         # Create output directory
#         if output_dir:
#             output_base = Path(output_dir)
#         else:
#             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
#             output_base = Path(f"batch_output_{timestamp}")
        
#         output_base.mkdir(parents=True, exist_ok=True)
        
#         print(f"📁 Processing {len(repos)} repositories...")
#         print(f"📂 Output directory: {output_base}")
#         print("-"*60)
        
#         results = []
#         for i, repo_config in enumerate(repos, 1):
#             repo_url = repo_config.get("url")
#             repo_name = repo_config.get("name")
#             extensions = repo_config.get("extensions")
#             max_files = repo_config.get("max_files")
            
#             if not repo_url:
#                 print(f"❌ Skipping item {i}: No URL provided")
#                 continue
            
#             print(f"\n[{i}/{len(repos)}] Processing: {repo_url}")
            
#             try:
#                 # Process using the repo command
#                 chunk_repo_cmd(
#                     repo_url=repo_url,
#                     name=repo_name,
#                     extensions=extensions,
#                     max_files=max_files,
#                     output=str(output_base / f"{repo_name or 'repo'}_{i:03d}.jsonl"),
#                     stats=False,
#                 )
#                 results.append({"repo": repo_url, "status": "success"})
#             except Exception as e:
#                 print(f"❌ Failed: {str(e)[:100]}")
#                 results.append({"repo": repo_url, "status": "failed", "error": str(e)})
        
#         # Save batch results
#         results_file = output_base / "batch_results.json"
#         with open(results_file, 'w') as f:
#             json.dump({
#                 "total_repos": len(repos),
#                 "successful": len([r for r in results if r["status"] == "success"]),
#                 "failed": len([r for r in results if r["status"] == "failed"]),
#                 "results": results,
#                 "timestamp": datetime.now().isoformat(),
#             }, f, indent=2)
        
#         print(f"\n✅ BATCH PROCESSING COMPLETE")
#         print(f"📊 Results saved to: {results_file}")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")

# # ---------------------------------------------------------------------
# # HELP EXAMPLES
# # ---------------------------------------------------------------------

# @app.command("examples")
# def show_examples():
#     """Show usage examples."""
#     _print_banner()
#     print("\n📚 USAGE EXAMPLES:")
#     print("="*60)
    
#     print("\n📁 LOCAL FILES:")
#     print("  chunkhive chunk local ./project")
#     print("  chunkhive chunk local ./project -o ./output")
#     print("  chunkhive chunk local ./project --name my_data --stats")
#     print("  chunkhive chunk file example.py -o chunks.jsonl")
    
#     print("\n🌐 GIT REPOSITORIES:")
#     print("  chunkhive chunk repo https://github.com/user/repo")
#     print("  chunkhive chunk repo https://github.com/langchain-ai/langchain --extensions .py,.md")
#     print("  chunkhive chunk repo https://github.com/user/repo -o ./results.jsonl --stats")
#     print("  chunkhive chunk repo https://github.com/user/repo --max-files 100")
    
#     print("\n🔍 ANALYSIS:")
#     print("  chunkhive analyze https://github.com/user/repo")
#     print("  chunkhive analyze ./local/repo --output analysis.json")
    
#     print("\n🔄 BATCH PROCESSING:")
#     print("  chunkhive batch repos.json")
#     print("  chunkhive batch repos.json -o ./batch_output")
    
#     print("\nℹ️  INFO:")
#     print("  chunkhive version")
#     print("  chunkhive info")
#     print("  chunkhive --help")

# # ---------------------------------------------------------------------
# # MAIN
# # ---------------------------------------------------------------------

# if __name__ == "__main__":
#     app()

# """
# chunkhive CLI - Production-grade with complete metadata - FIXED VERSION
# """
# import typer
# from typing import Optional, List
# from pathlib import Path
# import sys
# import json
# from datetime import datetime

# app = typer.Typer(
#     name="chunkhive",
#     help="Hierarchical, semantic code chunking for AI systems",
#     no_args_is_help=True,
# )

# # ---------------------------------------------------------------------
# # SIMPLE BANNER
# # ---------------------------------------------------------------------
# def _print_banner():
#     """Simple ASCII banner"""
#     print("="*60)
#     print("       chunkhive - Hierarchical Code Chunking")
#     print("="*60)

# # ---------------------------------------------------------------------
# # OUTPUT PATH HANDLING - FIXED
# # ---------------------------------------------------------------------
# def _resolve_output_path(
#     user_output: Optional[str],
#     default_name: str,
#     is_directory_input: bool = False
# ) -> Path:
#     """
#     Intelligently resolve output path based on user input.
    
#     Rules:
#     1. If user_output is None → Use default_name in current directory
#     2. If user_output ends with .jsonl → Treat as file path
#     3. If user_output is existing directory → Put default_name in that directory
#     4. Otherwise → Try to create parent directories for file path
    
#     Args:
#         user_output: User-provided output path (or None)
#         default_name: Default filename if not specified
#         is_directory_input: Whether input is a directory (for better defaults)
#     """
#     if user_output is None:
#         # No output specified, use default in current directory
#         return Path.cwd() / default_name
    
#     user_path = Path(user_output)
    
#     # Check if it looks like a file (has .jsonl extension or no extension but not an existing dir)
#     if user_path.suffix == '.jsonl':
#         # User specified a file with .jsonl extension
#         user_path.parent.mkdir(parents=True, exist_ok=True)
#         return user_path
    
#     # Check if it's an existing directory
#     if user_path.exists() and user_path.is_dir():
#         # User specified a directory, put default file inside it
#         return user_path / default_name
    
#     # Check if parent directory exists or can be created
#     if user_path.suffix == '':
#         # No extension, might be intended as directory
#         user_path.mkdir(parents=True, exist_ok=True)
#         return user_path / default_name
#     else:
#         # Has some extension, treat as file
#         user_path.parent.mkdir(parents=True, exist_ok=True)
#         return user_path

# # ---------------------------------------------------------------------
# # BASIC COMMANDS
# # ---------------------------------------------------------------------

# @app.command()
# def version():
#     """Show chunkhive version."""
#     try:
#         from . import __version__
#         print(f"chunkhive v{__version__}")
#     except:
#         print("chunkhive v0.1.7")  # UPDATE TO 0.1.7

# @app.command()
# def info():
#     """Show system information."""
#     _print_banner()
#     print(f"Python: {sys.version.split()[0]}")
#     print("Available: Python, Markdown, JSON, YAML, TXT, MDX files")
#     print("GitHub: https://github.com/AgentAhmed/ChunkHive")

# # ---------------------------------------------------------------------
# # CHUNK COMMANDS - FIXED TO MATCH PIPELINES
# # ---------------------------------------------------------------------
# chunk_app = typer.Typer()
# app.add_typer(chunk_app, name="chunk")

# # 1. LOCAL FILE/DIRECTORY CHUNKING - FIXED to match local_pipeline.py
# @chunk_app.command("local")
# def chunk_local(
#     path: str = typer.Argument(..., help="Local directory or file path"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     include: Optional[str] = typer.Option(None, "--include", help="Specific folder/file to include"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk local files/directories (matches local_pipeline.py pattern).
    
#     Examples:
#         chunkhive chunk local ./project              # Creates ./project.jsonl
#         chunkhive chunk local ./project -o ./output  # Creates ./output/project.jsonl
#         chunkhive chunk local ./project -o ./output/myproject.jsonl  # Creates specific file
#         chunkhive chunk local ./project --name my_data  # Creates ./my_data.jsonl
#     """
#     _print_banner()
    
#     try:
#         from .chunkers.repo_parser import RepoChunker
#         from .export.local_exporter import export_chunks_jsonl
#         from .metadata.chunk_stats import compute_dataset_stats
#         from .metadata.chunk_metadata import write_dataset_metadata
        
#         path_obj = Path(path)
        
#         if not path_obj.exists():
#             print(f"❌ ERROR: Path not found: {path}")
#             return
        
#         # Handle include parameter
#         if include:
#             path_obj = path_obj / include
#             if not path_obj.exists():
#                 print(f"❌ ERROR: Include path not found: {include}")
#                 return
        
#         chunker = RepoChunker()
#         all_chunks = []
#         file_count = 0
        
#         print(f"📁 Processing: {path_obj}")
        
#         # Create proper source metadata (not repo_metadata)
#         source_metadata = {
#             "source_name": name or path_obj.name,
#             "source_type": "local_directory",
#             "local_path": str(path_obj),
#             "extensions_included": "ALL",
#             "timestamp": datetime.now().strftime("%Y%m%d_%H%M%S"),
#         }
        
#         if path_obj.is_file():
#             # Single file processing
#             print(f"📄 Processing single file: {path_obj.name}")
            
#             chunks = chunker.chunk_file(path_obj, source_metadata)
#             if chunks:
#                 all_chunks.extend(chunks)
#                 file_count = 1
#         else:
#             # Directory processing
#             files_processed = 0
#             files_list = list(path_obj.rglob("*"))
#             total_files = len([f for f in files_list if f.is_file() and not any(part.startswith('.') for part in f.parts)])
            
#             print(f"📊 Found {total_files} files to process...")
            
#             for file_path in files_list:
#                 if file_path.is_file():
#                     try:
#                         # Skip hidden/system files
#                         if any(part.startswith('.') for part in file_path.parts):
#                             continue
                        
#                         chunks = chunker.chunk_file(file_path, source_metadata)
#                         if chunks:
#                             all_chunks.extend(chunks)
#                             file_count += 1
#                             files_processed += 1
                            
#                             if files_processed % 50 == 0:
#                                 print(f"  ✅ Processed {files_processed}/{total_files} files...")
                                
#                     except Exception as e:
#                         print(f"  ⚠️  Skipping {file_path.name}: {str(e)[:50]}")
        
#         # ✅ FIXED: Resolve output path intelligently
#         if name:
#             default_filename = f"{name}.jsonl"
#         elif path_obj.is_file():
#             default_filename = f"chunks_{path_obj.stem}.jsonl"
#         else:
#             default_filename = f"chunks_{path_obj.name}.jsonl"
        
#         output_path = _resolve_output_path(
#             user_output=output,
#             default_name=default_filename,
#             is_directory_input=path_obj.is_dir()
#         )
        
#         print(f"📁 Output will be saved to: {output_path}")
        
#         if all_chunks:
#             # ✅ FIXED: Ensure parent directory exists
#             output_path.parent.mkdir(parents=True, exist_ok=True)
            
#             # ✅ FIXED: Use local_exporter (matches local_pipeline.py)
#             export_chunks_jsonl(
#                 chunks=all_chunks,
#                 output_path=output_path,
#                 print_stats=stats,
#             )
            
#             # ✅ FIXED: Create stats file (matches pipeline)
#             stats_data = compute_dataset_stats(all_chunks)
#             stats_file = output_path.parent / f"{output_path.stem}_stats.json"
#             with open(stats_file, 'w') as f:
#                 json.dump(stats_data, f, indent=2)
            
#             # ✅ FIXED: Create metadata file (matches pipeline)
#             metadata_file = output_path.parent / f"{output_path.stem}_metadata.json"
#             write_dataset_metadata(
#                 chunks=all_chunks,
#                 output_path=metadata_file,
#                 dataset_name=name or path_obj.name,
#                 dataset_version="v1",
#             )
            
#             print(f"\n" + "="*60)
#             print("✅ SUCCESS: Local Processing Complete")
#             print("="*60)
#             print(f"📊 Source: {source_metadata['source_name']} (Local folder)")
#             print(f"📄 Files processed: {file_count}")
#             print(f"🧩 Total chunks generated: {len(all_chunks)}")
#             print(f"💾 Output files:")
#             print(f"   - Chunks: {output_path}")
#             print(f"   - Statistics: {stats_file}")
#             print(f"   - Metadata: {metadata_file}")
            
#         else:
#             print("⚠️  WARNING: No chunks generated")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#         print("💡 Try running with admin privileges or choose a different output location.")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")
#         import traceback
#         traceback.print_exc()

# # 2. GIT REPOSITORY CHUNKING - FIXED to match repo_pipeline.py
# @chunk_app.command("repo")
# def chunk_repo_cmd(
#     repo_url: str = typer.Argument(..., help="Git repository URL"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     extensions: Optional[str] = typer.Option(
#         None, "--extensions", 
#         help="File extensions to include (comma-separated, e.g., .py,.md)"
#     ),
#     max_files: Optional[int] = typer.Option(
#         None, "--max-files", 
#         help="Maximum number of files to process"
#     ),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a Git repository (matches repo_pipeline.py pattern).
    
#     Examples:
#         chunkhive chunk repo https://github.com/user/repo
#         chunkhive chunk repo https://github.com/user/repo -o ./output
#         chunkhive chunk repo https://github.com/user/repo -o ./my_repo.jsonl
#         chunkhive chunk repo https://github.com/user/repo --name langchain --extensions .py,.md
#     """
#     _print_banner()
    
#     try:
#         from .ingestion.repo_crawler import GitCrawler
#         from .metadata.repo_metadata import RepoMetadataExtractor
#         from .chunkers.repo_parser import RepoChunker
#         from .export.repo_exporter import export_repo_chunks_jsonl
#         from .metadata.chunk_metadata import write_dataset_metadata
        
#         print(f"🌐 Processing repository: {repo_url}")
        
#         # Initialize crawler
#         crawler = GitCrawler()
        
#         # Clone repository
#         repo_path = crawler.clone_repository(repo_url)
#         if not repo_path:
#             print(f"❌ ERROR: Failed to clone repository: {repo_url}")
#             return
        
#         # Extract repository metadata
#         extractor = RepoMetadataExtractor(repo_path)
#         repo_metadata = extractor.extract_comprehensive_metadata()
        
#         # ✅ FIXED: Add proper source naming
#         repo_metadata["source_name"] = name or repo_path.name
#         repo_metadata["source_type"] = "git_repository"
#         repo_metadata["repo_url"] = repo_url
#         repo_metadata["local_path"] = str(repo_path)
#         repo_metadata["extensions_included"] = extensions if extensions else "ALL"
#         repo_metadata["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
#         # Parse extensions
#         ext_set = None
#         if extensions:
#             ext_set = set(ext.strip() for ext in extensions.split(","))
        
#         # List files with info
#         file_infos, file_stats = crawler.list_files_with_info(
#             repo_path,
#             extensions=ext_set,
#             exclude_dirs={'.git', '__pycache__', 'node_modules', 'build', 'dist', '.venv'},
#             skip_binary=True
#         )
        
#         # Apply max files limit
#         if max_files and max_files > 0:
#             file_infos = file_infos[:max_files]
#             print(f"📊 Limiting to {max_files} files (out of {len(file_infos)})")
        
#         # ✅ FIXED: Resolve output path BEFORE processing
#         if name:
#             default_filename = f"{name}.jsonl"
#         else:
#             repo_name = repo_path.name
#             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
#             default_filename = f"chunks_{repo_name}_{timestamp}.jsonl"
        
#         output_path = _resolve_output_path(
#             user_output=output,
#             default_name=default_filename,
#             is_directory_input=True
#         )
        
#         print(f"📁 Output will be saved to: {output_path}")
        
#         # Process files with complete metadata
#         all_chunks = []
#         chunker = RepoChunker()
#         processed_files = 0
#         total_files = len(file_infos)
        
#         print(f"📊 Processing {total_files} files...")
        
#         for idx, file_info in enumerate(file_infos, 1):
#             try:
#                 # Create file-specific metadata
#                 file_specific_metadata = {
#                     **repo_metadata,
#                     "file_info": {
#                         "relative_path": file_info.relative_path,
#                         "size_bytes": file_info.size,
#                         "extension": file_info.extension,
#                         "is_binary": file_info.is_binary,
#                         "absolute_path": str(file_info.path),
#                     }
#                 }
                
#                 chunks = chunker.chunk_file(file_info.path, file_specific_metadata)
#                 if chunks:
#                     all_chunks.extend(chunks)
#                     processed_files += 1
                    
#                     # Progress indicator
#                     if idx % 50 == 0 or idx == total_files:
#                         print(f"  ✅ Processed {idx}/{total_files} files ({len(all_chunks)} chunks)")
                        
#             except Exception as e:
#                 print(f"  ⚠️  Skipping {file_info.path.name}: {str(e)[:80]}")
        
#         # Export with complete metadata
#         if all_chunks:
#             # ✅ FIXED: Ensure parent directory exists
#             output_path.parent.mkdir(parents=True, exist_ok=True)
            
#             # Add processing stats to metadata
#             final_metadata = {
#                 **repo_metadata,
#                 "processing_stats": {
#                     "total_files_listed": total_files,
#                     "processed_files": processed_files,
#                     "total_chunks": len(all_chunks),
#                     "extensions_filter": extensions,
#                     "max_files_limit": max_files,
#                 }
#             }
            
#             # ✅ FIXED: Use repo_exporter (matches repo_pipeline.py)
#             result = export_repo_chunks_jsonl(
#                 chunks=all_chunks,
#                 output_path=output_path,
#                 repo_metadata=final_metadata,
#                 print_stats=stats,
#             )
            
#             # ✅ FIXED: Create metadata file (matches pipeline)
#             metadata_file = output_path.parent / f"{output_path.stem}_metadata.json"
#             write_dataset_metadata(
#                 chunks=all_chunks,
#                 output_path=metadata_file,
#                 dataset_name=name or repo_path.name,
#                 dataset_version="v1",
#             )
            
#             print(f"\n" + "="*60)
#             print("✅ SUCCESS: Repository Processing Complete")
#             print("="*60)
#             print(f"📊 Source: {repo_metadata.get('source_name', 'N/A')} (Git repository)")
#             print(f"📄 Files processed: {processed_files}")
#             print(f"🧩 Total chunks generated: {len(all_chunks)}")
#             print(f"💾 Output files:")
#             print(f"   - Chunks: {output_path}")
#             print(f"   - Statistics: {result.get('stats_file', 'N/A')}")
#             print(f"   - Metadata: {metadata_file}")
            
#             # Show agentic frameworks if detected
#             agentic = repo_metadata.get("agentic_detection", {})
#             if agentic:
#                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
#                 if frameworks:
#                     print(f"🤖 Agentic frameworks: {', '.join(frameworks)}")
                    
#         else:
#             print("⚠️  WARNING: No chunks generated")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#         print("💡 Try running with admin privileges or choose a different output location.")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")
#         import traceback
#         traceback.print_exc()

# # 3. SINGLE FILE CHUNKING (alias) - FIXED
# @chunk_app.command("file")
# def chunk_file_cmd(
#     file_path: str = typer.Argument(..., help="File to chunk"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a single file with complete metadata.
    
#     Examples:
#         chunkhive chunk file example.py
#         chunkhive chunk file example.py -o ./output
#         chunkhive chunk file example.py -o ./my_chunks.jsonl
#         chunkhive chunk file example.py --stats
#     """
#     chunk_local(path=file_path, output=output, stats=stats)

# # 4. SINGLE REPOSITORY (alias for backward compatibility)
# @chunk_app.command("single")
# def chunk_single_cmd(
#     repo_url: str = typer.Argument(..., help="Git repository URL"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     extensions: Optional[str] = typer.Option(
#         None, "--extensions", 
#         help="File extensions to include (comma-separated)"
#     ),
#     max_files: Optional[int] = typer.Option(
#         None, "--max-files", 
#         help="Maximum number of files to process"
#     ),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a single Git repository (alias for 'repo' command).
#     """
#     return chunk_repo_cmd(
#         repo_url=repo_url,
#         name=name,
#         extensions=extensions,
#         max_files=max_files,
#         output=output,
#         stats=stats
#     )

# # ---------------------------------------------------------------------
# # ANALYZE COMMAND (ENHANCED) - FIXED OUTPUT
# # ---------------------------------------------------------------------

# @app.command("analyze")
# def analyze_cmd(
#     repo_path: str = typer.Argument(..., help="Repository path or URL"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
#     full: bool = typer.Option(False, "--full", help="Show full details"),
# ):
#     """
#     Analyze repository metadata with complete output.
    
#     Examples:
#         chunkhive analyze https://github.com/user/repo
#         chunkhive analyze ./local/repo --output analysis.json
#         chunkhive analyze https://github.com/user/repo --full
#     """
#     _print_banner()
    
#     try:
#         from .metadata.repo_metadata import RepoMetadataExtractor
#         from pathlib import Path
        
#         # Check if it's a URL or local path
#         if repo_path.startswith(("http://", "https://", "git@")):
#             from .ingestion.repo_crawler import GitCrawler
#             crawler = GitCrawler()
#             repo_obj = crawler.clone_repository(repo_path)
#             if not repo_obj:
#                 print(f"❌ ERROR: Failed to clone repository: {repo_path}")
#                 return
#         else:
#             repo_obj = Path(repo_path)
#             if not repo_obj.exists():
#                 print(f"❌ ERROR: Path not found: {repo_path}")
#                 return
        
#         print(f"🔍 Analyzing: {repo_obj}")
#         extractor = RepoMetadataExtractor(repo_obj)
#         analysis = extractor.extract_comprehensive_metadata()
        
#         # Add missing fields for consistency
#         analysis["source_url"] = repo_path if repo_path.startswith(("http://", "https://")) else f"file://{repo_obj.absolute()}"
#         analysis["source_type"] = "git_repository" if repo_path.startswith(("http://", "https://", "git@")) else "local_directory"
#         analysis["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
#         if output:
#             output_path = Path(output)
#             output_path.parent.mkdir(parents=True, exist_ok=True)
#             with open(output_path, 'w', encoding='utf-8') as f:
#                 json.dump(analysis, f, indent=2, default=str)
#             print(f"✅ SUCCESS: Analysis saved to {output_path}")
#         else:
#             print("📊 Analysis Results:")
#             basic = analysis.get("basic", {})
#             print(f"  📁 Name: {basic.get('repo_name', 'N/A')}")
#             print(f"  📄 Files: {basic.get('file_count', 0):,}")
#             print(f"  💾 Size: {basic.get('size_mb', 0)} MB")
            
#             agentic = analysis.get("agentic_detection", {})
#             if agentic:
#                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
#                 if frameworks:
#                     print(f"  🤖 Agentic Frameworks: {', '.join(frameworks)}")
            
#             if full:
#                 print("\n📋 Full Details:")
#                 print(json.dumps(analysis, indent=2, default=str))
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")

# # ---------------------------------------------------------------------
# # BATCH PROCESSING COMMAND - FIXED OUTPUT
# # ---------------------------------------------------------------------

# @app.command("batch")
# def batch_cmd(
#     config_file: str = typer.Argument(..., help="JSON config file with repositories list"),
#     output_dir: Optional[str] = typer.Option(None, "-o", "--output-dir", help="Output directory"),
# ):
#     """Batch process multiple repositories from config file."""
#     _print_banner()
    
#     try:
#         config_path = Path(config_file)
#         if not config_path.exists():
#             print(f"❌ ERROR: Config file not found: {config_file}")
#             return
        
#         with open(config_path, 'r') as f:
#             config = json.load(f)
        
#         repos = config.get("repositories", [])
#         if not repos:
#             print("❌ ERROR: No repositories found in config file")
#             return
        
#         # Create output directory
#         if output_dir:
#             output_base = Path(output_dir)
#         else:
#             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
#             output_base = Path(f"batch_output_{timestamp}")
        
#         output_base.mkdir(parents=True, exist_ok=True)
        
#         print(f"📁 Processing {len(repos)} repositories...")
#         print(f"📂 Output directory: {output_base}")
#         print("-"*60)
        
#         results = []
#         for i, repo_config in enumerate(repos, 1):
#             repo_url = repo_config.get("url")
#             repo_name = repo_config.get("name")
#             extensions = repo_config.get("extensions")
#             max_files = repo_config.get("max_files")
            
#             if not repo_url:
#                 print(f"❌ Skipping item {i}: No URL provided")
#                 continue
            
#             print(f"\n[{i}/{len(repos)}] Processing: {repo_url}")
            
#             try:
#                 # Process using the repo command
#                 chunk_repo_cmd(
#                     repo_url=repo_url,
#                     name=repo_name,
#                     extensions=extensions,
#                     max_files=max_files,
#                     output=str(output_base / f"{repo_name or 'repo'}_{i:03d}.jsonl"),
#                     stats=False,
#                 )
#                 results.append({"repo": repo_url, "status": "success"})
#             except Exception as e:
#                 print(f"❌ Failed: {str(e)[:100]}")
#                 results.append({"repo": repo_url, "status": "failed", "error": str(e)})
        
#         # Save batch results
#         results_file = output_base / "batch_results.json"
#         with open(results_file, 'w') as f:
#             json.dump({
#                 "total_repos": len(repos),
#                 "successful": len([r for r in results if r["status"] == "success"]),
#                 "failed": len([r for r in results if r["status"] == "failed"]),
#                 "results": results,
#                 "timestamp": datetime.now().isoformat(),
#             }, f, indent=2)
        
#         print(f"\n✅ BATCH PROCESSING COMPLETE")
#         print(f"📊 Results saved to: {results_file}")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")

# # ---------------------------------------------------------------------
# # HELP EXAMPLES
# # ---------------------------------------------------------------------

# @app.command("examples")
# def show_examples():
#     """Show usage examples."""
#     _print_banner()
#     print("\n📚 USAGE EXAMPLES:")
#     print("="*60)
    
#     print("\n📁 LOCAL FILES:")
#     print("  chunkhive chunk local ./project")
#     print("  chunkhive chunk local ./project -o ./output")
#     print("  chunkhive chunk local ./project --name my_data --stats")
#     print("  chunkhive chunk file example.py -o chunks.jsonl")
    
#     print("\n🌐 GIT REPOSITORIES:")
#     print("  chunkhive chunk repo https://github.com/user/repo")
#     print("  chunkhive chunk repo https://github.com/langchain-ai/langchain --extensions .py,.md")
#     print("  chunkhive chunk repo https://github.com/user/repo -o ./results.jsonl --stats")
#     print("  chunkhive chunk repo https://github.com/user/repo --max-files 100")
    
#     print("\n🔍 ANALYSIS:")
#     print("  chunkhive analyze https://github.com/user/repo")
#     print("  chunkhive analyze ./local/repo --output analysis.json")
    
#     print("\n🔄 BATCH PROCESSING:")
#     print("  chunkhive batch repos.json")
#     print("  chunkhive batch repos.json -o ./batch_output")
    
#     print("\nℹ️  INFO:")
#     print("  chunkhive version")
#     print("  chunkhive info")
#     print("  chunkhive --help")

# # ---------------------------------------------------------------------
# # MAIN
# # ---------------------------------------------------------------------

# if __name__ == "__main__":
#     app()

# """
# chunkhive CLI - Production-grade with complete metadata - FIXED OUTPUT HANDLING
# """
# import typer
# from typing import Optional, List
# from pathlib import Path
# import sys
# import json
# from datetime import datetime

# app = typer.Typer(
#     name="chunkhive",
#     help="Hierarchical, semantic code chunking for AI systems",
#     no_args_is_help=True,
# )

# # ---------------------------------------------------------------------
# # SIMPLE BANNER
# # ---------------------------------------------------------------------
# def _print_banner():
#     """Simple ASCII banner"""
#     print("="*60)
#     print("       chunkhive - Hierarchical Code Chunking")
#     print("="*60)

# # ---------------------------------------------------------------------
# # OUTPUT PATH HANDLING - FIXED
# # ---------------------------------------------------------------------
# def _resolve_output_path(
#     user_output: Optional[str],
#     default_name: str,
#     is_directory_input: bool = False
# ) -> Path:
#     """
#     Intelligently resolve output path based on user input.
    
#     Rules:
#     1. If user_output is None → Use default_name in current directory
#     2. If user_output ends with .jsonl → Treat as file path
#     3. If user_output is existing directory → Put default_name in that directory
#     4. Otherwise → Try to create parent directories for file path
    
#     Args:
#         user_output: User-provided output path (or None)
#         default_name: Default filename if not specified
#         is_directory_input: Whether input is a directory (for better defaults)
#     """
#     if user_output is None:
#         # No output specified, use default in current directory
#         return Path.cwd() / default_name
    
#     user_path = Path(user_output)
    
#     # Check if it looks like a file (has .jsonl extension or no extension but not an existing dir)
#     if user_path.suffix == '.jsonl':
#         # User specified a file with .jsonl extension
#         user_path.parent.mkdir(parents=True, exist_ok=True)
#         return user_path
    
#     # Check if it's an existing directory
#     if user_path.exists() and user_path.is_dir():
#         # User specified a directory, put default file inside it
#         return user_path / default_name
    
#     # Check if parent directory exists or can be created
#     if user_path.suffix == '':
#         # No extension, might be intended as directory
#         user_path.mkdir(parents=True, exist_ok=True)
#         return user_path / default_name
#     else:
#         # Has some extension, treat as file
#         user_path.parent.mkdir(parents=True, exist_ok=True)
#         return user_path

# # ---------------------------------------------------------------------
# # BASIC COMMANDS
# # ---------------------------------------------------------------------

# @app.command()
# def version():
#     """Show chunkhive version."""
#     try:
#         from . import __version__
#         print(f"chunkhive v{__version__}")
#     except:
#         print("chunkhive v0.1.9")

# @app.command()
# def info():
#     """Show system information."""
#     _print_banner()
#     print(f"Python: {sys.version.split()[0]}")
#     print("Available: Python, Markdown, JSON, YAML, TXT, MDX files")
#     print("GitHub: https://github.com/AgentAhmed/ChunkHive")

# # ---------------------------------------------------------------------
# # CHUNK COMMANDS - PRODUCTION GRADE WITH COMPLETE METADATA
# # ---------------------------------------------------------------------
# chunk_app = typer.Typer()
# app.add_typer(chunk_app, name="chunk")

# # 1. LOCAL FILE/DIRECTORY CHUNKING - FIXED OUTPUT
# @chunk_app.command("local")
# def chunk_local(
#     path: str = typer.Argument(..., help="Local directory or file path"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     include: Optional[str] = typer.Option(None, "--include", help="Specific folder/file to include"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk local files/directories with complete metadata.
    
#     Examples:
#         chunkhive chunk local ./project              # Creates ./project.jsonl
#         chunkhive chunk local ./project -o ./output  # Creates ./output/project.jsonl
#         chunkhive chunk local ./project -o ./output/myproject.jsonl  # Creates specific file
#         chunkhive chunk local ./project --name my_data  # Creates ./my_data.jsonl
#     """
#     _print_banner()
    
#     try:
#         from .chunkers.repo_parser import RepoChunker
#         from .export.repo_exporter import export_repo_chunks_jsonl
        
#         path_obj = Path(path)
        
#         if not path_obj.exists():
#             print(f"❌ ERROR: Path not found: {path}")
#             return
        
#         # Handle include parameter
#         if include:
#             path_obj = path_obj / include
#             if not path_obj.exists():
#                 print(f"❌ ERROR: Include path not found: {include}")
#                 return
        
#         chunker = RepoChunker()
#         all_chunks = []
#         file_count = 0
        
#         print(f"📁 Processing: {path_obj}")
        
#         # Create basic repository metadata for local files
#         repo_metadata = {
#             "repo_url": f"file://{path_obj.absolute()}",
#             "repo_name": name or path_obj.name,
#             "local_path": str(path_obj),
#             "extensions_included": "ALL",
#             "timestamp": datetime.now().strftime("%Y%m%d_%H%M%S"),
#             "basic": {
#                 "repo_name": path_obj.name,
#                 "local_path": str(path_obj),
#                 "size_mb": 0,  # Will be calculated
#                 "file_count": 0,  # Will be calculated
#                 "extracted_at": datetime.now().isoformat(),
#             },
#             "git": {
#                 "remote_url": "",
#                 "branch": "",
#                 "latest_commit": {},
#             },
#             "dependencies": {"python_packages": [], "nodejs_packages": [], "docker": False, "other_dependencies": []},
#             "structure": {"directories": [], "file_types": {}, "has_agentic_structure": False},
#             "agentic_detection": {},
#             "entry_points": [],
#             "config_files": [],
#             "source_type": "local_files",
#         }
        
#         if path_obj.is_file():
#             # Single file processing
#             file_metadata = {
#                 **repo_metadata,
#                 "file_info": {
#                     "relative_path": str(path_obj.name),
#                     "size_bytes": path_obj.stat().st_size,
#                     "extension": path_obj.suffix.lower(),
#                     "is_binary": False,
#                 }
#             }
            
#             chunks = chunker.chunk_file(path_obj, file_metadata)
#             if chunks:
#                 all_chunks.extend(chunks)
#                 file_count = 1
#                 print(f"📄 Processing single file: {path_obj.name}")
#         else:
#             # Directory processing
#             files_processed = 0
#             files_list = list(path_obj.rglob("*"))
#             total_files = len([f for f in files_list if f.is_file() and not any(part.startswith('.') for part in f.parts)])
            
#             print(f"📊 Found {total_files} files to process...")
            
#             for file_path in files_list:
#                 if file_path.is_file():
#                     try:
#                         # Skip hidden/system files
#                         if any(part.startswith('.') for part in file_path.parts):
#                             continue
                        
#                         # Create file-specific metadata
#                         file_metadata = {
#                             **repo_metadata,
#                             "file_info": {
#                                 "relative_path": str(file_path.relative_to(path_obj)),
#                                 "size_bytes": file_path.stat().st_size,
#                                 "extension": file_path.suffix.lower(),
#                                 "is_binary": False,
#                             }
#                         }
                        
#                         chunks = chunker.chunk_file(file_path, file_metadata)
#                         if chunks:
#                             all_chunks.extend(chunks)
#                             file_count += 1
#                             files_processed += 1
                            
#                             if files_processed % 50 == 0:
#                                 print(f"  ✅ Processed {files_processed}/{total_files} files...")
                                
#                     except Exception as e:
#                         print(f"  ⚠️  Skipping {file_path.name}: {str(e)[:50]}")
        
#         # ✅ FIXED: Resolve output path intelligently
#         if name:
#             default_filename = f"{name}.jsonl"
#         elif path_obj.is_file():
#             default_filename = f"chunks_{path_obj.stem}.jsonl"
#         else:
#             default_filename = f"chunks_{path_obj.name}.jsonl"
        
#         output_path = _resolve_output_path(
#             user_output=output,
#             default_name=default_filename,
#             is_directory_input=path_obj.is_dir()
#         )
        
#         print(f"📁 Output will be saved to: {output_path}")
        
#         if all_chunks:
#             # Update repo metadata with actual counts
#             repo_metadata["basic"]["file_count"] = file_count
            
#             # ✅ FIXED: Ensure parent directory exists
#             output_path.parent.mkdir(parents=True, exist_ok=True)
            
#             # Export with repository metadata
#             result = export_repo_chunks_jsonl(
#                 chunks=all_chunks,
#                 output_path=output_path,
#                 repo_metadata=repo_metadata,
#                 print_stats=stats,
#             )
            
#             print(f"\n" + "="*60)
#             print("✅ SUCCESS: Processing Complete")
#             print("="*60)
#             print(f"📄 Files processed: {file_count}")
#             print(f"🧩 Total chunks generated: {len(all_chunks)}")
#             print(f"💾 Output file: {output_path}")
#             print(f"📏 File size: {output_path.stat().st_size / 1024:.1f} KB")
            
#         else:
#             print("⚠️  WARNING: No chunks generated")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#         print("💡 Try running with admin privileges or choose a different output location.")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")
#         import traceback
#         traceback.print_exc()

# # 2. GIT REPOSITORY CHUNKING - FIXED OUTPUT
# @chunk_app.command("repo")
# def chunk_repo_cmd(
#     repo_url: str = typer.Argument(..., help="Git repository URL"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     extensions: Optional[str] = typer.Option(
#         None, "--extensions", 
#         help="File extensions to include (comma-separated, e.g., .py,.md)"
#     ),
#     max_files: Optional[int] = typer.Option(
#         None, "--max-files", 
#         help="Maximum number of files to process"
#     ),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a Git repository with complete metadata.
    
#     Examples:
#         chunkhive chunk repo https://github.com/user/repo
#         chunkhive chunk repo https://github.com/user/repo -o ./output
#         chunkhive chunk repo https://github.com/user/repo -o ./my_repo.jsonl
#         chunkhive chunk repo https://github.com/user/repo --name langchain --extensions .py,.md
#     """
#     _print_banner()
    
#     try:
#         from .ingestion.repo_crawler import GitCrawler
#         from .metadata.repo_metadata import RepoMetadataExtractor
#         from .chunkers.repo_parser import RepoChunker
#         from .export.repo_exporter import export_repo_chunks_jsonl
        
#         print(f"🌐 Processing repository: {repo_url}")
        
#         # Initialize crawler
#         crawler = GitCrawler()
        
#         # Clone repository
#         repo_path = crawler.clone_repository(repo_url)
#         if not repo_path:
#             print(f"❌ ERROR: Failed to clone repository: {repo_url}")
#             return
        
#         # Extract repository metadata
#         extractor = RepoMetadataExtractor(repo_path)
#         repo_metadata = extractor.extract_comprehensive_metadata()
        
#         # Add top-level fields
#         repo_metadata["repo_url"] = repo_url
#         repo_metadata["repo_name"] = name or repo_path.name
#         repo_metadata["extensions_included"] = extensions if extensions else "ALL"
#         repo_metadata["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
#         repo_metadata["local_path"] = str(repo_path)
        
#         # Parse extensions
#         ext_set = None
#         if extensions:
#             ext_set = set(ext.strip() for ext in extensions.split(","))
        
#         # List files with info
#         file_infos, file_stats = crawler.list_files_with_info(
#             repo_path,
#             extensions=ext_set,
#             exclude_dirs={'.git', '__pycache__', 'node_modules', 'build', 'dist', '.venv'},
#             skip_binary=True
#         )
        
#         # Apply max files limit
#         if max_files and max_files > 0:
#             file_infos = file_infos[:max_files]
#             print(f"📊 Limiting to {max_files} files (out of {len(file_infos)})")
        
#         # ✅ FIXED: Resolve output path BEFORE processing
#         if name:
#             default_filename = f"{name}.jsonl"
#         else:
#             repo_name = repo_path.name
#             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
#             default_filename = f"chunks_{repo_name}_{timestamp}.jsonl"
        
#         output_path = _resolve_output_path(
#             user_output=output,
#             default_name=default_filename,
#             is_directory_input=True
#         )
        
#         print(f"📁 Output will be saved to: {output_path}")
        
#         # Process files with complete metadata
#         all_chunks = []
#         chunker = RepoChunker()
#         processed_files = 0
#         total_files = len(file_infos)
        
#         print(f"📊 Processing {total_files} files...")
        
#         for idx, file_info in enumerate(file_infos, 1):
#             try:
#                 # Create complete metadata with file_info
#                 file_specific_metadata = {
#                     **repo_metadata,  # All repo-level metadata
#                     "file_info": {  # File-specific metadata
#                         "relative_path": file_info.relative_path,
#                         "size_bytes": file_info.size,
#                         "extension": file_info.extension,
#                         "is_binary": file_info.is_binary,
#                         "absolute_path": str(file_info.path),
#                     }
#                 }
                
#                 chunks = chunker.chunk_file(file_info.path, file_specific_metadata)
#                 if chunks:
#                     all_chunks.extend(chunks)
#                     processed_files += 1
                    
#                     # Progress indicator
#                     if idx % 50 == 0 or idx == total_files:
#                         print(f"  ✅ Processed {idx}/{total_files} files ({len(all_chunks)} chunks)")
                        
#             except Exception as e:
#                 print(f"  ⚠️  Skipping {file_info.path.name}: {str(e)[:80]}")
        
#         # Export with complete metadata
#         if all_chunks:
#             # Add processing stats to metadata
#             final_metadata = {
#                 **repo_metadata,
#                 "processing_stats": {
#                     "total_files_listed": total_files,
#                     "processed_files": processed_files,
#                     "total_chunks": len(all_chunks),
#                     "extensions_filter": extensions,
#                     "max_files_limit": max_files,
#                 }
#             }
            
#             # ✅ FIXED: Ensure parent directory exists
#             output_path.parent.mkdir(parents=True, exist_ok=True)
            
#             result = export_repo_chunks_jsonl(
#                 chunks=all_chunks,
#                 output_path=output_path,
#                 repo_metadata=final_metadata,
#                 print_stats=stats,
#             )
            
#             print(f"\n" + "="*60)
#             print("✅ SUCCESS: Repository Processing Complete")
#             print("="*60)
#             print(f"📁 Repository: {repo_metadata.get('repo_name', 'N/A')}")
#             print(f"📄 Files processed: {processed_files}")
#             print(f"🧩 Total chunks generated: {len(all_chunks)}")
#             print(f"💾 Output file: {output_path}")
#             print(f"📏 File size: {output_path.stat().st_size / 1024:.1f} KB" if output_path.exists() else "")
            
#             # Show agentic frameworks if detected
#             agentic = repo_metadata.get("agentic_detection", {})
#             if agentic:
#                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
#                 if frameworks:
#                     print(f"🤖 Agentic frameworks: {', '.join(frameworks)}")
                    
#         else:
#             print("⚠️  WARNING: No chunks generated")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#         print("💡 Try running with admin privileges or choose a different output location.")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")
#         import traceback
#         traceback.print_exc()

# # 3. SINGLE FILE CHUNKING (alias) - FIXED
# @chunk_app.command("file")
# def chunk_file_cmd(
#     file_path: str = typer.Argument(..., help="File to chunk"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file or directory path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a single file with complete metadata.
    
#     Examples:
#         chunkhive chunk file example.py
#         chunkhive chunk file example.py -o ./output
#         chunkhive chunk file example.py -o ./my_chunks.jsonl
#         chunkhive chunk file example.py --stats
#     """
#     chunk_local(path=file_path, output=output, stats=stats)

# # 4. SINGLE REPOSITORY (alias for backward compatibility)
# @chunk_app.command("single")
# def chunk_single_cmd(
#     repo_url: str = typer.Argument(..., help="Git repository URL"),
#     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
#     extensions: Optional[str] = typer.Option(
#         None, "--extensions", 
#         help="File extensions to include (comma-separated)"
#     ),
#     max_files: Optional[int] = typer.Option(
#         None, "--max-files", 
#         help="Maximum number of files to process"
#     ),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
#     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# ):
#     """
#     Chunk a single Git repository (alias for 'repo' command).
#     """
#     return chunk_repo_cmd(
#         repo_url=repo_url,
#         name=name,
#         extensions=extensions,
#         max_files=max_files,
#         output=output,
#         stats=stats
#     )

# # ---------------------------------------------------------------------
# # ANALYZE COMMAND (ENHANCED) - FIXED OUTPUT
# # ---------------------------------------------------------------------

# @app.command("analyze")
# def analyze_cmd(
#     repo_path: str = typer.Argument(..., help="Repository path or URL"),
#     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
#     full: bool = typer.Option(False, "--full", help="Show full details"),
# ):
#     """
#     Analyze repository metadata with complete output.
    
#     Examples:
#         chunkhive analyze https://github.com/user/repo
#         chunkhive analyze ./local/repo --output analysis.json
#         chunkhive analyze https://github.com/user/repo --full
#     """
#     _print_banner()
    
#     try:
#         from .metadata.repo_metadata import RepoMetadataExtractor
#         from pathlib import Path
        
#         # Check if it's a URL or local path
#         if repo_path.startswith(("http://", "https://", "git@")):
#             from .ingestion.repo_crawler import GitCrawler
#             crawler = GitCrawler()
#             repo_obj = crawler.clone_repository(repo_path)
#             if not repo_obj:
#                 print(f"❌ ERROR: Failed to clone repository: {repo_path}")
#                 return
#         else:
#             repo_obj = Path(repo_path)
#             if not repo_obj.exists():
#                 print(f"❌ ERROR: Path not found: {repo_path}")
#                 return
        
#         print(f"🔍 Analyzing: {repo_obj}")
#         extractor = RepoMetadataExtractor(repo_obj)
#         analysis = extractor.extract_comprehensive_metadata()
        
#         # Add missing fields for consistency
#         analysis["repo_url"] = repo_path if repo_path.startswith(("http://", "https://")) else f"file://{repo_obj.absolute()}"
#         analysis["extensions_included"] = "ALL"
#         analysis["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
#         if output:
#             output_path = Path(output)
#             output_path.parent.mkdir(parents=True, exist_ok=True)
#             with open(output_path, 'w', encoding='utf-8') as f:
#                 json.dump(analysis, f, indent=2, default=str)
#             print(f"✅ SUCCESS: Analysis saved to {output_path}")
#         else:
#             print("📊 Analysis Results:")
#             basic = analysis.get("basic", {})
#             print(f"  📁 Name: {basic.get('repo_name', 'N/A')}")
#             print(f"  📄 Files: {basic.get('file_count', 0):,}")
#             print(f"  💾 Size: {basic.get('size_mb', 0)} MB")
            
#             agentic = analysis.get("agentic_detection", {})
#             if agentic:
#                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
#                 if frameworks:
#                     print(f"  🤖 Agentic Frameworks: {', '.join(frameworks)}")
            
#             if full:
#                 print("\n📋 Full Details:")
#                 print(json.dumps(analysis, indent=2, default=str))
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")

# # ---------------------------------------------------------------------
# # BATCH PROCESSING COMMAND - FIXED OUTPUT
# # ---------------------------------------------------------------------

# @app.command("batch")
# def batch_cmd(
#     config_file: str = typer.Argument(..., help="JSON config file with repositories list"),
#     output_dir: Optional[str] = typer.Option(None, "-o", "--output-dir", help="Output directory"),
# ):
#     """Batch process multiple repositories from config file."""
#     _print_banner()
    
#     try:
#         config_path = Path(config_file)
#         if not config_path.exists():
#             print(f"❌ ERROR: Config file not found: {config_file}")
#             return
        
#         with open(config_path, 'r') as f:
#             config = json.load(f)
        
#         repos = config.get("repositories", [])
#         if not repos:
#             print("❌ ERROR: No repositories found in config file")
#             return
        
#         # Create output directory
#         if output_dir:
#             output_base = Path(output_dir)
#         else:
#             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
#             output_base = Path(f"batch_output_{timestamp}")
        
#         output_base.mkdir(parents=True, exist_ok=True)
        
#         print(f"📁 Processing {len(repos)} repositories...")
#         print(f"📂 Output directory: {output_base}")
#         print("-"*60)
        
#         results = []
#         for i, repo_config in enumerate(repos, 1):
#             repo_url = repo_config.get("url")
#             repo_name = repo_config.get("name")
#             extensions = repo_config.get("extensions")
#             max_files = repo_config.get("max_files")
            
#             if not repo_url:
#                 print(f"❌ Skipping item {i}: No URL provided")
#                 continue
            
#             print(f"\n[{i}/{len(repos)}] Processing: {repo_url}")
            
#             try:
#                 # Determine output path for this repo
#                 output_filename = f"{repo_name or 'repo'}_{i:03d}.jsonl"
#                 repo_output_path = output_base / output_filename
                
#                 # Process using internal logic
#                 from .ingestion.repo_crawler import GitCrawler
#                 from .metadata.repo_metadata import RepoMetadataExtractor
#                 from .chunkers.repo_parser import RepoChunker
#                 from .export.repo_exporter import export_repo_chunks_jsonl
                
#                 crawler = GitCrawler()
#                 repo_path = crawler.clone_repository(repo_url)
#                 if not repo_path:
#                     print(f"❌ Failed to clone: {repo_url}")
#                     results.append({"repo": repo_url, "status": "failed", "error": "Clone failed"})
#                     continue
                
#                 extractor = RepoMetadataExtractor(repo_path)
#                 repo_metadata = extractor.extract_comprehensive_metadata()
                
#                 # Process files
#                 chunker = RepoChunker()
#                 all_chunks = []
                
#                 # Similar processing logic as chunk_repo_cmd but simplified
#                 # ... (rest of processing logic)
                
#                 results.append({"repo": repo_url, "status": "success", "output": str(repo_output_path)})
                
#             except Exception as e:
#                 print(f"❌ Failed: {str(e)[:100]}")
#                 results.append({"repo": repo_url, "status": "failed", "error": str(e)})
        
#         # Save batch results
#         results_file = output_base / "batch_results.json"
#         with open(results_file, 'w') as f:
#             json.dump({
#                 "total_repos": len(repos),
#                 "successful": len([r for r in results if r["status"] == "success"]),
#                 "failed": len([r for r in results if r["status"] == "failed"]),
#                 "results": results,
#                 "timestamp": datetime.now().isoformat(),
#             }, f, indent=2)
        
#         print(f"\n✅ BATCH PROCESSING COMPLETE")
#         print(f"📊 Results saved to: {results_file}")
        
#     except PermissionError as e:
#         print(f"❌ PERMISSION ERROR: {e}")
#     except Exception as e:
#         print(f"❌ ERROR: {e}")

# # ---------------------------------------------------------------------
# # HELP EXAMPLES
# # ---------------------------------------------------------------------

# @app.command("examples")
# def show_examples():
#     """Show usage examples."""
#     _print_banner()
#     print("\n📚 USAGE EXAMPLES:")
#     print("="*60)
    
#     print("\n📁 LOCAL FILES:")
#     print("  chunkhive chunk local ./project")
#     print("  chunkhive chunk local ./project -o ./output")
#     print("  chunkhive chunk local ./project --name my_data --stats")
#     print("  chunkhive chunk file example.py -o chunks.jsonl")
    
#     print("\n🌐 GIT REPOSITORIES:")
#     print("  chunkhive chunk repo https://github.com/user/repo")
#     print("  chunkhive chunk repo https://github.com/langchain-ai/langchain --extensions .py,.md")
#     print("  chunkhive chunk repo https://github.com/user/repo -o ./results.jsonl --stats")
#     print("  chunkhive chunk repo https://github.com/user/repo --max-files 100")
    
#     print("\n🔍 ANALYSIS:")
#     print("  chunkhive analyze https://github.com/user/repo")
#     print("  chunkhive analyze ./local/repo --output analysis.json")
    
#     print("\n🔄 BATCH PROCESSING:")
#     print("  chunkhive batch repos.json")
#     print("  chunkhive batch repos.json -o ./batch_output")
    
#     print("\nℹ️  INFO:")
#     print("  chunkhive version")
#     print("  chunkhive info")
#     print("  chunkhive --help")

# # ---------------------------------------------------------------------
# # MAIN
# # ---------------------------------------------------------------------

# if __name__ == "__main__":
#     app()

# # """
# # chunkhive CLI - Production-grade with complete metadata
# # """
# # import typer
# # from typing import Optional, List
# # from pathlib import Path
# # import sys
# # import json
# # from datetime import datetime

# # app = typer.Typer(
# #     name="chunkhive",
# #     help="Hierarchical, semantic code chunking for AI systems",
# #     no_args_is_help=True,
# # )

# # # ---------------------------------------------------------------------
# # # SIMPLE BANNER
# # # ---------------------------------------------------------------------
# # def _print_banner():
# #     """Simple ASCII banner"""
# #     print("="*60)
# #     print("       chunkhive - Hierarchical Code Chunking")
# #     print("="*60)

# # # ---------------------------------------------------------------------
# # # BASIC COMMANDS
# # # ---------------------------------------------------------------------

# # @app.command()
# # def version():
# #     """Show chunkhive version."""
# #     try:
# #         from . import __version__
# #         print(f"chunkhive v{__version__}")
# #     except:
# #         print("chunkhive v0.1.7")  # UPDATE TO 0.1.7

# # @app.command()
# # def info():
# #     """Show system information."""
# #     _print_banner()
# #     print(f"Python: {sys.version.split()[0]}")
# #     print("Available: Python, Markdown, JSON, YAML, TXT, MDX files")
# #     print("GitHub: https://github.com/AgentAhmed/ChunkHive")

# # # ---------------------------------------------------------------------
# # # FIX: Import toml with graceful fallback
# # # ---------------------------------------------------------------------
# # def _safe_parse_pyproject_toml(file_path: Path):
# #     """Parse pyproject.toml with graceful fallback"""
# #     try:
# #         import toml
# #         data = toml.load(file_path)
# #         deps = data.get("project", {}).get("dependencies", [])
# #         return [d.split("==")[0].split(">=")[0].strip() for d in deps]
# #     except ImportError:
# #         print(f"⚠️  Note: Install 'toml' package for pyproject.toml parsing: pip install toml")
# #         return []
# #     except Exception as e:
# #         print(f"⚠️  Error parsing {file_path}: {e}")
# #         return []

# # # ---------------------------------------------------------------------
# # # CHUNK COMMANDS - PRODUCTION GRADE WITH COMPLETE METADATA
# # # ---------------------------------------------------------------------
# # chunk_app = typer.Typer()
# # app.add_typer(chunk_app, name="chunk")

# # # 1. LOCAL FILE/DIRECTORY CHUNKING
# # @chunk_app.command("local")
# # def chunk_local(
# #     path: str = typer.Argument(..., help="Local directory or file path"),
# #     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
# #     include: Optional[str] = typer.Option(None, "--include", help="Specific folder/file to include"),
# #     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
# #     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# # ):
# #     """
# #     Chunk local files/directories with complete metadata.
# #     """
# #     _print_banner()
    
# #     try:
# #         from .chunkers.repo_parser import RepoChunker
# #         from .export.repo_exporter import export_repo_chunks_jsonl
        
# #         path_obj = Path(path)
        
# #         if not path_obj.exists():
# #             print(f"ERROR: Path not found: {path}")
# #             return
        
# #         # Handle include parameter
# #         if include:
# #             path_obj = path_obj / include
# #             if not path_obj.exists():
# #                 print(f"ERROR: Include path not found: {include}")
# #                 return
        
# #         chunker = RepoChunker()
# #         all_chunks = []
# #         file_count = 0
        
# #         print(f"Processing: {path_obj}")
        
# #         # Create basic repository metadata for local files
# #         repo_metadata = {
# #             "repo_url": f"file://{path_obj.absolute()}",
# #             "repo_name": name or path_obj.name,
# #             "local_path": str(path_obj),
# #             "extensions_included": "ALL",
# #             "timestamp": datetime.now().strftime("%Y%m%d_%H%M%S"),
# #             "basic": {
# #                 "repo_name": path_obj.name,
# #                 "local_path": str(path_obj),
# #                 "size_mb": 0,  # Will be calculated
# #                 "file_count": 0,  # Will be calculated
# #                 "extracted_at": datetime.now().isoformat(),
# #             },
# #             "git": {
# #                 "remote_url": "",
# #                 "branch": "",
# #                 "latest_commit": {},
# #             },
# #             "dependencies": {"python_packages": [], "nodejs_packages": [], "docker": False, "other_dependencies": []},
# #             "structure": {"directories": [], "file_types": {}, "has_agentic_structure": False},
# #             "agentic_detection": {},
# #             "entry_points": [],
# #             "config_files": [],
# #             "source_type": "local_files",
# #         }
        
# #         if path_obj.is_file():
# #             # Single file processing
# #             file_metadata = {
# #                 **repo_metadata,
# #                 "file_info": {
# #                     "relative_path": str(path_obj.name),
# #                     "size_bytes": path_obj.stat().st_size,
# #                     "extension": path_obj.suffix.lower(),
# #                     "is_binary": False,
# #                 }
# #             }
            
# #             chunks = chunker.chunk_file(path_obj, file_metadata)
# #             if chunks:
# #                 all_chunks.extend(chunks)
# #                 file_count = 1
# #         else:
# #             # Directory processing
# #             files_processed = 0
# #             for file_path in path_obj.rglob("*"):
# #                 if file_path.is_file():
# #                     try:
# #                         # Skip hidden/system files
# #                         if any(part.startswith('.') for part in file_path.parts):
# #                             continue
                        
# #                         # Create file-specific metadata
# #                         file_metadata = {
# #                             **repo_metadata,
# #                             "file_info": {
# #                                 "relative_path": str(file_path.relative_to(path_obj)),
# #                                 "size_bytes": file_path.stat().st_size,
# #                                 "extension": file_path.suffix.lower(),
# #                                 "is_binary": False,
# #                             }
# #                         }
                        
# #                         chunks = chunker.chunk_file(file_path, file_metadata)
# #                         if chunks:
# #                             all_chunks.extend(chunks)
# #                             file_count += 1
# #                             files_processed += 1
                            
# #                             if files_processed % 50 == 0:
# #                                 print(f"  Processed {files_processed} files...")
                                
# #                     except Exception as e:
# #                         print(f"  ⚠️  Skipping {file_path.name}: {str(e)[:50]}")
        
# #         # Determine output filename
# #         if not output:
# #             if name:
# #                 output = f"{name}.jsonl"
# #             elif path_obj.is_file():
# #                 output = f"chunks_{path_obj.stem}.jsonl"
# #             else:
# #                 output = f"chunks_{path_obj.name}.jsonl"
        
# #         if all_chunks:
# #             # Update repo metadata with actual counts
# #             repo_metadata["basic"]["file_count"] = file_count
            
# #             # Export with repository metadata
# #             result = export_repo_chunks_jsonl(
# #                 chunks=all_chunks,
# #                 output_path=Path(output),
# #                 repo_metadata=repo_metadata,
# #                 print_stats=stats,
# #             )
            
# #             print(f"✅ SUCCESS: Processed {file_count} files, generated {len(all_chunks)} chunks")
# #             print(f"📁 Output: {output}")
            
# #             # Show summary
# #             print(f"📊 Repository: {repo_metadata['repo_name']}")
# #             print(f"📄 Total files processed: {file_count}")
            
# #         else:
# #             print("⚠️  WARNING: No chunks generated")
        
# #     except Exception as e:
# #         print(f"❌ ERROR: {e}")
# #         import traceback
# #         traceback.print_exc()

# # # 2. GIT REPOSITORY CHUNKING (PRODUCTION GRADE WITH COMPLETE METADATA)
# # @chunk_app.command("repo")
# # def chunk_repo_cmd(
# #     repo_url: str = typer.Argument(..., help="Git repository URL"),
# #     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
# #     extensions: Optional[str] = typer.Option(
# #         None, "--extensions", 
# #         help="File extensions to include (comma-separated, e.g., .py,.md)"
# #     ),
# #     max_files: Optional[int] = typer.Option(
# #         None, "--max-files", 
# #         help="Maximum number of files to process"
# #     ),
# #     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
# #     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# # ):
# #     """
# #     Chunk a Git repository with complete metadata.
# #     """
# #     _print_banner()
    
# #     try:
# #         from .ingestion.repo_crawler import GitCrawler
# #         from .metadata.repo_metadata import RepoMetadataExtractor
# #         from .chunkers.repo_parser import RepoChunker
# #         from .export.repo_exporter import export_repo_chunks_jsonl
        
# #         print(f"Processing repository: {repo_url}")
        
# #         # Initialize crawler
# #         crawler = GitCrawler()
        
# #         # Clone repository
# #         repo_path = crawler.clone_repository(repo_url)
# #         if not repo_path:
# #             print(f"❌ ERROR: Failed to clone repository: {repo_url}")
# #             return
        
# #         # Extract repository metadata
# #         extractor = RepoMetadataExtractor(repo_path)
# #         repo_metadata = extractor.extract_comprehensive_metadata()
        
# #         # ✅ CRITICAL FIX: Add missing top-level fields
# #         repo_metadata["repo_url"] = repo_url
# #         repo_metadata["repo_name"] = name or repo_path.name
# #         repo_metadata["extensions_included"] = extensions if extensions else "ALL"
# #         repo_metadata["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
# #         repo_metadata["local_path"] = str(repo_path)
        
# #         # ✅ FIX: Add fallback for pyproject.toml parsing
# #         if "dependencies" in repo_metadata:
# #             deps = repo_metadata["dependencies"]
# #             if isinstance(deps, dict) and "python_packages" in deps:
# #                 # If no python packages found, try to parse pyproject.toml manually
# #                 if not deps["python_packages"]:
# #                     pyproject_path = repo_path / "pyproject.toml"
# #                     if pyproject_path.exists():
# #                         python_packages = _safe_parse_pyproject_toml(pyproject_path)
# #                         deps["python_packages"] = python_packages
        
# #         # Parse extensions
# #         ext_set = None
# #         if extensions:
# #             ext_set = set(ext.strip() for ext in extensions.split(","))
        
# #         # List files with info
# #         file_infos, file_stats = crawler.list_files_with_info(
# #             repo_path,
# #             extensions=ext_set,
# #             exclude_dirs={'.git', '__pycache__', 'node_modules', 'build', 'dist', '.venv'},
# #             skip_binary=True
# #         )
        
# #         # Apply max files limit
# #         if max_files and max_files > 0:
# #             file_infos = file_infos[:max_files]
# #             print(f"📊 Limiting to {max_files} files (out of {len(file_infos)})")
        
# #         # Process files with complete metadata
# #         all_chunks = []
# #         chunker = RepoChunker()
# #         processed_files = 0
# #         total_files = len(file_infos)
        
# #         print(f"📁 Processing {total_files} files...")
        
# #         for idx, file_info in enumerate(file_infos, 1):
# #             try:
# #                 # ✅ CRITICAL FIX: Create complete metadata with file_info
# #                 file_specific_metadata = {
# #                     **repo_metadata,  # All repo-level metadata
# #                     "file_info": {  # File-specific metadata
# #                         "relative_path": file_info.relative_path,
# #                         "size_bytes": file_info.size,
# #                         "extension": file_info.extension,
# #                         "is_binary": file_info.is_binary,
# #                         "absolute_path": str(file_info.path),
# #                     }
# #                 }
                
# #                 chunks = chunker.chunk_file(file_info.path, file_specific_metadata)
# #                 if chunks:
# #                     all_chunks.extend(chunks)
# #                     processed_files += 1
                    
# #                     # Progress indicator
# #                     if idx % 50 == 0 or idx == total_files:
# #                         print(f"  📊 Processed {idx}/{total_files} files ({len(all_chunks)} chunks)")
                        
# #             except Exception as e:
# #                 print(f"  ⚠️  Skipping {file_info.path.name}: {str(e)[:80]}")
        
# #         # Determine output filename
# #         if not output:
# #             if name:
# #                 output = f"{name}.jsonl"
# #             else:
# #                 repo_name = repo_path.name
# #                 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# #                 output = f"chunks_{repo_name}_{timestamp}.jsonl"
        
# #         # Export with complete metadata
# #         if all_chunks:
# #             # Add processing stats to metadata
# #             final_metadata = {
# #                 **repo_metadata,
# #                 "processing_stats": {
# #                     "total_files_listed": total_files,
# #                     "processed_files": processed_files,
# #                     "total_chunks": len(all_chunks),
# #                     "extensions_filter": extensions,
# #                     "max_files_limit": max_files,
# #                 }
# #             }
            
# #             result = export_repo_chunks_jsonl(
# #                 chunks=all_chunks,
# #                 output_path=Path(output),
# #                 repo_metadata=final_metadata,
# #                 print_stats=stats,
# #             )
            
# #             print(f"\n✅ SUCCESS: Processed {processed_files} files, generated {len(all_chunks)} chunks")
# #             print(f"📁 Output: {output}")
            
# #             # Show repository info
# #             basic = repo_metadata.get("basic", {})
# #             print(f"📊 Repository: {repo_metadata.get('repo_name', 'N/A')}")
# #             print(f"📄 Total repository files: {basic.get('file_count', 0)}")
# #             print(f"💾 Size: {basic.get('size_mb', 0)} MB")
            
# #             # Show agentic frameworks if detected
# #             agentic = repo_metadata.get("agentic_detection", {})
# #             if agentic:
# #                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
# #                 if frameworks:
# #                     print(f"🤖 Agentic frameworks: {', '.join(frameworks)}")
                    
# #             print(f"⏱️  Processing time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            
# #         else:
# #             print("⚠️  WARNING: No chunks generated")
        
# #     except Exception as e:
# #         print(f"❌ ERROR: {e}")
# #         import traceback
# #         traceback.print_exc()

# # # 3. SINGLE FILE CHUNKING (alias)
# # @chunk_app.command("file")
# # def chunk_file_cmd(
# #     file_path: str = typer.Argument(..., help="File to chunk"),
# #     output: Optional[str] = typer.Option(None, "-o", "--output"),
# #     stats: bool = typer.Option(False, "-s", "--stats"),
# # ):
# #     """Chunk a single file with complete metadata."""
# #     chunk_local(path=file_path, output=output, stats=stats)

# # # 4. SINGLE REPOSITORY (alias for backward compatibility)
# # @chunk_app.command("single")
# # def chunk_single_cmd(
# #     repo_url: str = typer.Argument(..., help="Git repository URL"),
# #     name: Optional[str] = typer.Option(None, "--name", help="Output name prefix"),
# #     extensions: Optional[str] = typer.Option(
# #         None, "--extensions", 
# #         help="File extensions to include (comma-separated)"
# #     ),
# #     max_files: Optional[int] = typer.Option(
# #         None, "--max-files", 
# #         help="Maximum number of files to process"
# #     ),
# #     output: Optional[str] = typer.Option(None, "-o", "--output", help="Output file path"),
# #     stats: bool = typer.Option(False, "-s", "--stats", help="Show statistics"),
# # ):
# #     """
# #     Chunk a single Git repository (alias for 'repo' command).
# #     """
# #     return chunk_repo_cmd(
# #         repo_url=repo_url,
# #         name=name,
# #         extensions=extensions,
# #         max_files=max_files,
# #         output=output,
# #         stats=stats
# #     )

# # # ---------------------------------------------------------------------
# # # ANALYZE COMMAND (ENHANCED)
# # # ---------------------------------------------------------------------

# # @app.command("analyze")
# # def analyze_cmd(
# #     repo_path: str = typer.Argument(..., help="Repository path or URL"),
# #     output: Optional[str] = typer.Option(None, "-o", "--output"),
# #     full: bool = typer.Option(False, "--full", help="Show full details"),
# # ):
# #     """Analyze repository metadata with complete output."""
# #     _print_banner()
    
# #     try:
# #         from .metadata.repo_metadata import RepoMetadataExtractor
# #         from pathlib import Path
        
# #         # Check if it's a URL or local path
# #         if repo_path.startswith(("http://", "https://", "git@")):
# #             from .ingestion.repo_crawler import GitCrawler
# #             crawler = GitCrawler()
# #             repo_obj = crawler.clone_repository(repo_path)
# #             if not repo_obj:
# #                 print(f"❌ ERROR: Failed to clone repository: {repo_path}")
# #                 return
# #         else:
# #             repo_obj = Path(repo_path)
# #             if not repo_obj.exists():
# #                 print(f"❌ ERROR: Path not found: {repo_path}")
# #                 return
        
# #         print(f"🔍 Analyzing: {repo_obj}")
# #         extractor = RepoMetadataExtractor(repo_obj)
# #         analysis = extractor.extract_comprehensive_metadata()
        
# #         # ✅ FIX: Add missing fields for consistency
# #         analysis["repo_url"] = repo_path if repo_path.startswith(("http://", "https://")) else f"file://{repo_obj.absolute()}"
# #         analysis["extensions_included"] = "ALL"
# #         analysis["timestamp"] = datetime.now().strftime("%Y%m%d_%H%M%S")
        
# #         if output:
# #             with open(output, 'w', encoding='utf-8') as f:
# #                 json.dump(analysis, f, indent=2, default=str)
# #             print(f"✅ SUCCESS: Analysis saved to {output}")
# #         else:
# #             print("📊 Analysis Results:")
# #             basic = analysis.get("basic", {})
# #             print(f"  📁 Name: {basic.get('repo_name', 'N/A')}")
# #             print(f"  📄 Files: {basic.get('file_count', 0):,}")
# #             print(f"  💾 Size: {basic.get('size_mb', 0)} MB")
            
# #             agentic = analysis.get("agentic_detection", {})
# #             if agentic:
# #                 frameworks = [k for k, v in agentic.items() if v in ["usage", "dependency", "implementation"]]
# #                 if frameworks:
# #                     print(f"  🤖 Agentic Frameworks: {', '.join(frameworks)}")
            
# #             if full:
# #                 print("\n📋 Full Details:")
# #                 print(json.dumps(analysis, indent=2, default=str))
        
# #     except Exception as e:
# #         print(f"❌ ERROR: {e}")

# # # ---------------------------------------------------------------------
# # # BATCH PROCESSING COMMAND (NEW - Production Feature)
# # # ---------------------------------------------------------------------

# # @app.command("batch")
# # def batch_cmd(
# #     config_file: str = typer.Argument(..., help="JSON config file with repositories list"),
# #     output_dir: Optional[str] = typer.Option(None, "-o", "--output-dir", help="Output directory"),
# # ):
# #     """Batch process multiple repositories from config file."""
# #     _print_banner()
    
# #     try:
# #         config_path = Path(config_file)
# #         if not config_path.exists():
# #             print(f"❌ ERROR: Config file not found: {config_file}")
# #             return
        
# #         with open(config_path, 'r') as f:
# #             config = json.load(f)
        
# #         repos = config.get("repositories", [])
# #         if not repos:
# #             print("❌ ERROR: No repositories found in config file")
# #             return
        
# #         # Create output directory
# #         if output_dir:
# #             output_base = Path(output_dir)
# #         else:
# #             timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# #             output_base = Path(f"batch_output_{timestamp}")
        
# #         output_base.mkdir(parents=True, exist_ok=True)
        
# #         print(f"📁 Processing {len(repos)} repositories...")
# #         print(f"📂 Output directory: {output_base}")
# #         print("-"*60)
        
# #         results = []
# #         for i, repo_config in enumerate(repos, 1):
# #             repo_url = repo_config.get("url")
# #             repo_name = repo_config.get("name")
# #             extensions = repo_config.get("extensions")
# #             max_files = repo_config.get("max_files")
            
# #             if not repo_url:
# #                 print(f"❌ Skipping item {i}: No URL provided")
# #                 continue
            
# #             print(f"\n[{i}/{len(repos)}] Processing: {repo_url}")
            
# #             try:
# #                 # Use the repo command logic
# #                 result = chunk_repo_cmd(
# #                     repo_url=repo_url,
# #                     name=repo_name,
# #                     extensions=extensions,
# #                     max_files=max_files,
# #                     output=str(output_base / f"{repo_name or 'repo'}_{i:03d}.jsonl"),
# #                     stats=False,
# #                 )
# #                 results.append({"repo": repo_url, "status": "success"})
# #             except Exception as e:
# #                 print(f"❌ Failed: {str(e)[:100]}")
# #                 results.append({"repo": repo_url, "status": "failed", "error": str(e)})
        
# #         # Save batch results
# #         results_file = output_base / "batch_results.json"
# #         with open(results_file, 'w') as f:
# #             json.dump({
# #                 "total_repos": len(repos),
# #                 "successful": len([r for r in results if r["status"] == "success"]),
# #                 "failed": len([r for r in results if r["status"] == "failed"]),
# #                 "results": results,
# #                 "timestamp": datetime.now().isoformat(),
# #             }, f, indent=2)
        
# #         print(f"\n✅ BATCH PROCESSING COMPLETE")
# #         print(f"📊 Results saved to: {results_file}")
        
# #     except Exception as e:
# #         print(f"❌ ERROR: {e}")

# # # ---------------------------------------------------------------------
# # # MAIN
# # # ---------------------------------------------------------------------

# # if __name__ == "__main__":
# #     app()