
"""
chunkhive: Hierarchical, semantic code chunking with embeddings for AI systems

Public API for the chunkhive package.
"""

__version__ = "0.3.1"

# Import Path at module level
from pathlib import Path
from typing import Optional

# Re-export key classes for easy access
from .chunkers.schema import (
    CodeChunk,
    ChunkAST,
    ChunkSpan,
    ChunkHierarchy,
    ChunkType,
    ASTSymbolType,
)

from .chunkers.hybrid_parser import HierarchicalChunker
from .chunkers.repo_parser import RepoChunker
from .metadata.repo_metadata import RepoMetadataExtractor
from .ingestion.repo_crawler import GitCrawler, RepoFileInfo

# Import the main functions
from .chunkers.ast_parser import extract_ast_chunks as chunk_python_ast
from .chunkers.text_parser import chunk_document as chunk_text

# Optional imports (only if available)
try:
    from .chunkers.javascript_parser import chunk_javascript_file
    HAS_JAVASCRIPT = True
except ImportError:
    HAS_JAVASCRIPT = False
    chunk_javascript_file = None

# Remove C++ import for now (you'll add it later)
HAS_CPP = False

# ---------------------------------------------------------------------
# PUBLIC API FUNCTIONS
# ---------------------------------------------------------------------

def chunk_file(file_path: str) -> list:
    """
    Universal file chunking - auto-detects language and uses appropriate chunker.
    """
    path = Path(file_path)
    
    if not path.exists():
        raise FileNotFoundError(f"File not found: {file_path}")
    
    suffix = path.suffix.lower()
    
    # Python files - use your advanced hierarchical chunker
    if suffix == '.py':
        chunker = HierarchicalChunker()
        return chunker.chunk_file(path)
    
    # JavaScript/TypeScript files
    elif suffix in ['.js', '.jsx', '.ts', '.tsx']:
        if HAS_JAVASCRIPT and chunk_javascript_file:
            return chunk_javascript_file(path)
        else:
            # Fallback to text chunking if JS support not installed
            return _fallback_to_text_chunking(path)
    
    # C++ files (placeholder for future)
    elif suffix in ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hh']:
        # For now, fallback to text chunking
        return _fallback_to_text_chunking(path)
    
    # Text/documentation files
    elif suffix in ['.md', '.txt', '.rst', '.mdx', '.html', '.htm']:
        return _chunk_text_file(path)
    
    # Configuration files
    elif suffix in ['.json', '.yaml', '.yml', '.toml', '.ini', '.cfg']:
        return _chunk_text_file(path)
    
    # Unknown file type - try as text
    else:
        return _chunk_text_file(path)


def chunk_repository(repo_path: str, use_hierarchical: bool = True) -> list:
    """
    Chunk an entire repository or directory.
    """
    path = Path(repo_path)
    
    if not path.exists():
        raise FileNotFoundError(f"Repository not found: {repo_path}")
    
    if not path.is_dir():
        raise ValueError(f"Path is not a directory: {repo_path}")
    
    repo_chunker = RepoChunker(use_hierarchical=use_hierarchical)
    all_chunks = []
    
    for file_path in path.rglob("*"):
        if file_path.is_file() and not _should_skip_file(file_path):
            try:
                chunks = repo_chunker.chunk_file(file_path, repo_metadata=None)
                all_chunks.extend(chunks)
            except Exception:
                continue
    
    return all_chunks


def analyze_repository(repo_path: str) -> dict:
    """
    Analyze a repository without chunking (metadata extraction).
    """
    path = Path(repo_path)
    
    if not path.exists():
        raise FileNotFoundError(f"Repository not found: {repo_path}")
    
    extractor = RepoMetadataExtractor(path)
    return extractor.extract_comprehensive_metadata()


def export_chunks(chunks: list, output_path: str, format: str = "jsonl", repo_mode: bool = False) -> None:
    """
    Export chunks to a file.
    """
    output = Path(output_path)
    output.parent.mkdir(parents=True, exist_ok=True)
    
    if format.lower() == "jsonl":
        if repo_mode:
            # Use repository exporter (adds metadata)
            from .export.repo_exporter import export_repo_chunks_jsonl
            repo_metadata = {}  # ✅ FIXED: Use empty dict, NOT None
            export_repo_chunks_jsonl(chunks, output, repo_metadata, print_stats=False)
        else:
            # Use local exporter
            from .export.local_exporter import export_chunks_jsonl
            export_chunks_jsonl(chunks, output, print_stats=False)
    
    elif format.lower() == "json":
        import json
        chunk_dicts = [_chunk_to_dict(c) for c in chunks]
        with open(output, 'w') as f:
            json.dump(chunk_dicts, f, indent=2)
    
    else:
        raise ValueError(f"Unsupported format: {format}. Use 'jsonl' or 'json'")


def export_local(chunks: list, output_path: str) -> None:
    """
    Export chunks using local exporter (simple format).
    """
    from .export.local_exporter import export_chunks_jsonl
    
    output = Path(output_path)
    export_chunks_jsonl(chunks, output, print_stats=True)


def export_repo(chunks: list, output_path: str, repo_metadata: Optional[dict] = None) -> dict:
    """
    Export chunks using repository exporter (with metadata).
    """
    from .export.repo_exporter import export_repo_chunks_jsonl
    
    output = Path(output_path)
    if repo_metadata is None:
        repo_metadata = {}  # ✅ FIXED: Convert None to empty dict
    
    return export_repo_chunks_jsonl(chunks, output, repo_metadata, print_stats=True)


# ---------------------------------------------------------------------
# HELPER FUNCTIONS (internal)
# ---------------------------------------------------------------------

def _chunk_text_file(file_path: Path) -> list:
    """Fallback chunking for text files."""
    try:
        content = file_path.read_text(encoding='utf-8', errors='ignore')
        return chunk_text(content, str(file_path))
    except Exception:
        return []


def _fallback_to_text_chunking(file_path: Path) -> list:
    """Fallback when language-specific chunker is not available."""
    try:
        content = file_path.read_text(encoding='utf-8', errors='ignore')
        if _looks_like_code(content):
            # Create a simple code chunk
            chunk = CodeChunk(
                chunk_id=f"code_{hash(str(file_path)) % 10000:04d}",
                file_path=str(file_path),
                language=file_path.suffix[1:] if file_path.suffix else "text",
                chunk_type="code",
                code=content,
                ast=ChunkAST(
                    symbol_type="code",
                    name=file_path.name,
                    parent=None
                ),
                span=ChunkSpan(
                    start_line=1,
                    end_line=len(content.split('\n'))
                ),
                hierarchy=ChunkHierarchy(is_primary=True)
            )
            return [chunk]
        else:
            return _chunk_text_file(file_path)
    except Exception:
        return []


def _should_skip_file(file_path: Path) -> bool:
    """Check if a file should be skipped during chunking."""
    if any(part.startswith('.') for part in file_path.parts):
        return True
    
    skip_dirs = {'.git', '__pycache__', 'node_modules', 'build', 'dist', '.venv', 'venv'}
    if any(part in skip_dirs for part in file_path.parts):
        return True
    
    # REMOVED: No file size limit
    # Users can implement their own limits if needed
    
    return False


def _looks_like_code(content: str) -> bool:
    """Simple heuristic to check if text looks like code."""
    import re
    code_patterns = [
        r'^\s*(def|class|import|from|function|var|let|const|if|for|while|return)\b',
        r'^\s*\{.*\}',
        r'^\s*\[.*\]',
        r'^\s*\(.*\)',
        r'^\s*//',
        r'^\s*/\*',
        r'^\s*#include',
    ]
    
    lines = content.split('\n')[:50]
    code_lines = 0
    
    for line in lines:
        if any(re.search(pattern, line) for pattern in code_patterns):
            code_lines += 1
    
    return code_lines >= 3


def _chunk_to_dict(chunk: CodeChunk) -> dict:
    """Convert a CodeChunk to dictionary for JSON export."""
    return {
        "chunk_id": chunk.chunk_id,
        "file_path": chunk.file_path,
        "language": chunk.language,
        "chunk_type": chunk.chunk_type,
        "code": chunk.code,
        "ast": {
            "symbol_type": chunk.ast.symbol_type,
            "name": chunk.ast.name,
            "parent": chunk.ast.parent,
            "docstring": chunk.ast.docstring,
            "decorators": chunk.ast.decorators,
            "imports": chunk.ast.imports,
        } if chunk.ast else None,
        "span": {
            "start_byte": chunk.span.start_byte,
            "end_byte": chunk.span.end_byte,
            "start_line": chunk.span.start_line,
            "end_line": chunk.span.end_line,
        } if chunk.span else None,
        "metadata": chunk.metadata,
        "hierarchy": {
            "parent_id": chunk.hierarchy.parent_id,
            "children_ids": chunk.hierarchy.children_ids,
            "depth": chunk.hierarchy.depth,
            "is_primary": chunk.hierarchy.is_primary,
            "is_extracted": chunk.hierarchy.is_extracted,
        } if getattr(chunk, "hierarchy", None) else None,
    }


# ---------------------------------------------------------------------
# MODULE EXPORTS
# ---------------------------------------------------------------------

__all__ = [
    "__version__",
    "chunk_file",
    "chunk_repository",
    "analyze_repository",
    "export_chunks",
    "export_local",
    "export_repo",
    "CodeChunk",
    "ChunkAST",
    "ChunkSpan",
    "ChunkHierarchy",
    "ChunkType",
    "ASTSymbolType",
    "HierarchicalChunker",
    "RepoChunker",
    "RepoMetadataExtractor",
    "GitCrawler",
    "RepoFileInfo",
    "chunk_python_ast",
    "chunk_text",
    "chunk_javascript_file",
    "HAS_JAVASCRIPT",
    "HAS_CPP",
]