"""
BlockMD Query API - Query interface for BlockMD AST.

This module provides flexible query capabilities for searching and filtering blocks.
Supports chainable query syntax with multiple filtering, sorting, and view options.
"""

from typing import List, Optional, Callable, Union, Any, Literal
from dataclasses import dataclass
from pathlib import Path
import os

# Import view formatters from export module
from .export import TreeView, TableView, ListView

# Import from parser module
from .parser import Block, BlockMDParser


# ============================================================================
# Query Builder (Chainable API)
# ============================================================================

class QueryBuilder:
    """
    Fluent API for querying BlockMD documents.
    
    Supports chainable methods for:
    - Loading from files/directories
    - Filtering by properties, content, level, custom predicates
    - Sorting by fields or custom functions
    - Outputting in tree or table format
    
    Example:
        >>> query().from_path('docs/').where(
        ...     tags='include', 
        ...     level=lambda l: l >= 2
        ... ).contains('TODO').order_by('title').view('table')
    """
    
    def __init__(self, blocks: Optional[List['Block']] = None, _is_filtered: bool = False):
        """
        Initialize query builder.
        
        Args:
            blocks: Initial list of blocks (optional)
            _is_filtered: Internal flag indicating if blocks are already flattened
        """
        self._blocks: List['Block'] = blocks or []
        self._all_blocks_cache: Optional[List['Block']] = None
        self._is_filtered = _is_filtered
    
    def from_path(self, path: Union[str, Path]) -> 'QueryBuilder':
        """
        Load blocks from file or directory.
        
        Args:
            path: Path to .bmd file or directory containing .bmd files
            
        Returns:
            Self for chaining
            
        Example:
            >>> query().from_path('docs/spec.bmd')
            >>> query().from_path('docs/')  # Load all .bmd files
        """
        from .parser import BlockMDParser
        
        parser = BlockMDParser()
        path = Path(path)
        
        if path.is_file():
            self._blocks.extend(parser.parse_file(str(path)))
        elif path.is_dir():
            # Load all .bmd files in directory
            for bmd_file in sorted(path.glob('**/*.bmd')):
                self._blocks.extend(parser.parse_file(str(bmd_file)))
        else:
            raise ValueError(f"Path not found: {path}")
        
        self._all_blocks_cache = None  # Invalidate cache
        return self
    
    def from_blocks(self, blocks: List['Block']) -> 'QueryBuilder':
        """
        Initialize from existing blocks.
        
        Args:
            blocks: List of Block objects
            
        Returns:
            Self for chaining
        """
        self._blocks = blocks
        self._all_blocks_cache = None
        return self
    
    def all_blocks(self) -> List['Block']:
        """
        Get all blocks (including nested children) with caching.
        
        Returns:
            Flattened list of all blocks
        """
        if self._all_blocks_cache is None:
            if self._is_filtered:
                # Already flattened, just return as is
                self._all_blocks_cache = self._blocks
            else:
                # Need to flatten hierarchy
                self._all_blocks_cache = []
                for root in self._blocks:
                    self._collect_blocks(root, self._all_blocks_cache)
        return self._all_blocks_cache
    
    def _collect_blocks(self, block: 'Block', result: List['Block']) -> None:
        """Recursively collect all blocks."""
        result.append(block)
        for child in block.children:
            self._collect_blocks(child, result)
    
    def where(self, **conditions) -> 'QueryBuilder':
        """
        Filter blocks by property conditions.
        
        Args:
            **conditions: Property filters as key=value pairs
                         Value can be:
                         - str/int: Exact match
                         - callable: Lambda function returning bool
                         - tuple/list: Check if property in values
            
        Returns:
            New QueryBuilder with filtered blocks
            
        Example:
            >>> query().where(status='done', level=2)
            >>> query().where(level=lambda l: l >= 2)
            >>> query().where(status=['todo', 'doing'])
        """
        filtered = []
        
        for block in self.all_blocks():
            match = True
            
            for key, condition in conditions.items():
                # Handle special keys
                if key == 'level':
                    value = block.level
                elif key == 'title':
                    value = block.title
                else:
                    # Regular property
                    if not block.has_property(key):
                        match = False
                        break
                    value = block.get_property(key)
                
                # Apply condition
                if callable(condition):
                    if not condition(value):
                        match = False
                        break
                elif isinstance(condition, (list, tuple)):
                    if value not in condition:
                        match = False
                        break
                else:
                    if str(value) != str(condition):
                        match = False
                        break
            
            if match:
                filtered.append(block)
        
        return QueryBuilder(filtered, _is_filtered=True)
    
    def has_property(self, *keys: str) -> 'QueryBuilder':
        """
        Filter blocks that have specified properties.
        
        Args:
            *keys: Property keys to check for
            
        Returns:
            New QueryBuilder with filtered blocks
            
        Example:
            >>> query().has_property('status', 'priority')
        """
        filtered = [
            block for block in self.all_blocks()
            if all(block.has_property(key) for key in keys)
        ]
        return QueryBuilder(filtered, _is_filtered=True)
    
    def contains(self, keyword: str, case_sensitive: bool = False) -> 'QueryBuilder':
        """
        Filter blocks whose content contains keyword.
        
        Args:
            keyword: Keyword to search for
            case_sensitive: Whether search is case-sensitive
            
        Returns:
            New QueryBuilder with filtered blocks
            
        Example:
            >>> query().contains('TODO')
            >>> query().contains('important', case_sensitive=True)
        """
        if case_sensitive:
            filtered = [
                block for block in self.all_blocks()
                if keyword in block.get_content_text()
            ]
        else:
            keyword_lower = keyword.lower()
            filtered = [
                block for block in self.all_blocks()
                if keyword_lower in block.get_content_text().lower()
            ]
        
        return QueryBuilder(filtered, _is_filtered=True)
    
    def filter(self, predicate: Callable[['Block'], bool]) -> 'QueryBuilder':
        """
        Filter blocks using custom predicate function.
        
        Args:
            predicate: Function that takes Block and returns bool
            
        Returns:
            New QueryBuilder with filtered blocks
            
        Example:
            >>> query().filter(lambda b: len(b.children) > 2)
            >>> query().filter(lambda b: b.is_leaf())
        """
        filtered = [block for block in self.all_blocks() if predicate(block)]
        return QueryBuilder(filtered, _is_filtered=True)
    
    def order_by(
        self, 
        key: Union[str, Callable[['Block'], Any]], 
        reverse: bool = False
    ) -> 'QueryBuilder':
        """
        Sort blocks by field or custom function.
        
        Args:
            key: Field name (str) or lambda function for sorting
            reverse: Sort in descending order
            
        Returns:
            New QueryBuilder with sorted blocks
            
        Example:
            >>> query().order_by('title')
            >>> query().order_by('level', reverse=True)
            >>> query().order_by(lambda b: len(b.content))
        """
        blocks = self.all_blocks().copy()
        
        if callable(key):
            blocks.sort(key=key, reverse=reverse)
        elif key == 'level':
            blocks.sort(key=lambda b: b.level, reverse=reverse)
        elif key == 'title':
            blocks.sort(key=lambda b: b.title, reverse=reverse)
        elif key == 'depth':
            blocks.sort(key=lambda b: b.get_depth(), reverse=reverse)
        else:
            # Sort by property value
            blocks.sort(
                key=lambda b: b.get_property(key, ''), 
                reverse=reverse
            )
        
        return QueryBuilder(blocks, _is_filtered=True)
    
    def limit(self, count: int) -> 'QueryBuilder':
        """
        Limit number of results.
        
        Args:
            count: Maximum number of blocks to return
            
        Returns:
            New QueryBuilder with limited blocks
        """
        return QueryBuilder(self.all_blocks()[:count], _is_filtered=True)
    
    def select(self, *fields: str) -> List[dict]:
        """
        Select specific fields from blocks.
        
        Args:
            *fields: Field names to extract (id, title, content, level, etc.)
            
        Returns:
            List of dictionaries with selected fields
            
        Example:
            >>> query().select('id', 'title', 'content')
        """
        results = []
        for block in self.all_blocks():
            item = {}
            for field in fields:
                if field == 'title':
                    item[field] = block.title
                elif field == 'level':
                    item[field] = block.level
                elif field == 'content':
                    item[field] = block.get_content_text()
                elif field == 'path':
                    item[field] = block.get_path_string()
                elif field == 'depth':
                    item[field] = block.get_depth()
                elif field == 'id':
                    item[field] = block.get_property('id', '')
                else:
                    # Try as property
                    item[field] = block.get_property(field, '')
            results.append(item)
        return results
    
    def view(
        self, 
        format: Literal['tree', 'table', 'list'] = 'list',
        fields: Optional[List[str]] = None
    ) -> str:
        """
        Format results for display.
        
        Args:
            format: Output format ('tree', 'table', or 'list')
            fields: Fields to include in output
            
        Returns:
            Formatted string representation
            
        Example:
            >>> print(query().where(level=2).view('tree'))
            >>> print(query().select_fields('title', 'status').view('table'))
        """
        blocks = self.all_blocks()
        
        if format == 'tree':
            return TreeView().render(blocks)
        elif format == 'table':
            fields = fields or ['title', 'level', 'path']
            return TableView(fields).render(blocks)
        else:  # list
            return ListView(fields).render(blocks)
    
    def execute(self) -> List['Block']:
        """
        Execute query and return blocks.
        
        Returns:
            List of matching blocks
        """
        return self.all_blocks()
    
    def count(self) -> int:
        """
        Count matching blocks.
        
        Returns:
            Number of blocks
        """
        return len(self.all_blocks())


def query(blocks: Optional[List['Block']] = None) -> QueryBuilder:
    """
    Create a new query builder.
    
    Args:
        blocks: Optional initial blocks
        
    Returns:
        New QueryBuilder instance
        
    Example:
        >>> query().from_path('docs/').where(status='done').view('table')
    """
    return QueryBuilder(blocks)



