"""
Parser module for extracting type hints and docstrings from callables.
"""

import inspect
import re
import warnings
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, get_args, get_origin


def extract_parameter_info(func: Callable) -> Dict[str, Dict[str, Any]]:
    """
    Extract parameter information from a callable including types and defaults.
    
    Args:
        func: The callable to extract parameter information from
        
    Returns:
        Dictionary mapping parameter names to their metadata
    """
    sig = inspect.signature(func)
    params_info = {}
    
    for param_name, param in sig.parameters.items():
        if param_name == 'self' or param_name == 'cls':
            continue
            
        param_type = param.annotation if param.annotation != inspect.Parameter.empty else None
        has_default = param.default != inspect.Parameter.empty
        default_value = param.default if has_default else None
        
        # Warn if no type annotation
        if param_type is None:
            warnings.warn(
                f"Parameter '{param_name}' in function '{func.__name__}' has no type annotation. "
                f"Defaulting to str type.",
                UserWarning
            )
            param_type = str
        
        params_info[param_name] = {
            'type': param_type,
            'default': default_value,
            'has_default': has_default,
            'required': not has_default
        }
    
    return params_info


def parse_docstring(func: Callable) -> Tuple[Optional[str], Dict[str, str]]:
    """
    Parse function docstring to extract description and parameter help text.
    
    Supports Google-style, NumPy-style, and reStructuredText docstrings.
    
    Args:
        func: The callable whose docstring to parse
        
    Returns:
        Tuple of (function description, dict of parameter names to help text)
    """
    docstring = inspect.getdoc(func)
    if not docstring:
        return None, {}
    
    lines = docstring.split('\n')
    description_lines = []
    param_help = {}
    
    in_args_section = False
    in_params_section = False
    current_param = None
    
    for line in lines:
        stripped = line.strip()
        
        # Check for Args/Arguments/Parameters section (Google/NumPy style)
        if re.match(r'^(Args|Arguments|Parameters):?\s*$', stripped, re.IGNORECASE):
            in_args_section = True
            in_params_section = False
            continue
        
        # Check for other sections that end Args section
        if re.match(r'^(Returns?|Raises?|Yields?|Examples?|Notes?|See Also):?\s*$', stripped, re.IGNORECASE):
            in_args_section = False
            in_params_section = False
            current_param = None
            continue
        
        if in_args_section:
            # Google style: param_name (type): description or param_name: description
            google_match = re.match(r'^(\w+)\s*(?:\([^)]+\))?\s*:\s*(.+)$', stripped)
            if google_match:
                current_param = google_match.group(1)
                param_help[current_param] = google_match.group(2)
                continue
            
            # NumPy style: param_name : type
            numpy_match = re.match(r'^(\w+)\s*:\s*(.*)$', stripped)
            if numpy_match and not google_match:
                current_param = numpy_match.group(1)
                param_help[current_param] = ""
                continue
            
            # Continuation of previous parameter description
            if current_param and stripped:
                if param_help[current_param]:
                    param_help[current_param] += " " + stripped
                else:
                    param_help[current_param] = stripped
                continue
        
        # reStructuredText style: :param name: description
        rst_match = re.match(r'^:param\s+(\w+):\s*(.+)$', stripped)
        if rst_match:
            param_name = rst_match.group(1)
            param_help[param_name] = rst_match.group(2)
            in_params_section = True
            continue
        
        # Collect description (before any special sections)
        if not in_args_section and not in_params_section and not re.match(r'^:', stripped):
            if stripped:
                description_lines.append(stripped)
    
    description = ' '.join(description_lines) if description_lines else None
    return description, param_help


def get_argparse_type(python_type: Any) -> Tuple[Any, Dict[str, Any]]:
    """
    Convert Python type hint to argparse-compatible type and options.
    
    Args:
        python_type: The Python type annotation
        
    Returns:
        Tuple of (argparse type, dict of additional argparse kwargs)
    """
    kwargs = {}
    
    # Handle None type
    if python_type is None or python_type is type(None):
        return str, kwargs
    
    # Get origin for generic types (List, Optional, Union, etc.)
    origin = get_origin(python_type)
    args = get_args(python_type)
    
    # Handle Optional[T] (which is Union[T, None])
    if origin is Union:
        # Filter out NoneType
        non_none_args = [arg for arg in args if arg is not type(None)]
        if len(non_none_args) == 1:
            # This is Optional[T], recurse with T
            return get_argparse_type(non_none_args[0])
        else:
            # Multiple non-None types in Union, default to str
            return str, kwargs
    
    # Handle List[T]
    if origin is list:
        kwargs['nargs'] = '+'
        if args:
            inner_type, _ = get_argparse_type(args[0])
            return inner_type, kwargs
        return str, kwargs
    
    # Handle basic types
    if python_type == bool:
        # Boolean flags are handled specially in add_argument
        return bool, kwargs
    elif python_type == int:
        return int, kwargs
    elif python_type == float:
        return float, kwargs
    elif python_type == str:
        return str, kwargs
    else:
        # For any other type, try to use it directly or fall back to str
        if callable(python_type):
            return python_type, kwargs
        return str, kwargs


def create_argument_name(param_name: str) -> str:
    """
    Convert parameter name to command-line argument name.
    
    Args:
        param_name: The parameter name from the function signature
        
    Returns:
        Command-line argument name (e.g., 'my_param' -> '--my-param')
    """
    return '--' + param_name.replace('_', '-')
