"""
Input Processor - Unified input classification and preprocessing

Handles classification and preprocessing of user input:
- Slash commands (/help, /save, /agent)
- Shell commands (!ls, !git status)
- File references (@filename)
- Natural language prompts

Examples:
    processor = InputProcessor(workspace_root=Path.cwd())

    # Classify input
    input_type = processor.classify("/help")
    # InputType.SLASH_COMMAND

    # Process input with expansion
    result = processor.preprocess("Look at @main.py")
    # ProcessedInput with file_references=["src/main.py"]
"""

import re
from pathlib import Path
from typing import Optional

from groknroll.repl.config import InputType, ProcessedInput
from groknroll.tui.fuzzy_search import FuzzyFileSearch


class InputProcessor:
    """
    Unified input classification and preprocessing

    Detects input types and expands file references for the REPL.
    Supports slash commands, shell commands, file references,
    and natural language.

    Example:
        processor = InputProcessor(workspace_root=Path.cwd())

        # Classify
        if processor.classify("!ls") == InputType.SHELL_COMMAND:
            print("Shell command detected")

        # Preprocess
        result = processor.preprocess("explain @utils.py")
        print(f"Files: {result.file_references}")
    """

    # Pattern to detect @references: @filename or @"filename with spaces"
    AT_PATTERN = re.compile(r'@(?:"([^"]+)"|([^\s@]+))')

    def __init__(
        self,
        workspace_root: Optional[Path] = None,
        fuzzy_search: Optional[FuzzyFileSearch] = None,
        expand_references: bool = True,
    ):
        """
        Initialize input processor

        Args:
            workspace_root: Root directory for file searches
            fuzzy_search: Custom FuzzyFileSearch instance
            expand_references: Auto-expand @references (default: True)
        """
        self.workspace_root = workspace_root or Path.cwd()
        self.fuzzy_search = fuzzy_search or FuzzyFileSearch(workspace_root=self.workspace_root)
        self.expand_references = expand_references

    def classify(self, text: str) -> InputType:
        """
        Classify input text into an InputType

        Args:
            text: Raw user input

        Returns:
            InputType classification

        Examples:
            classify("/help") -> InputType.SLASH_COMMAND
            classify("!ls -la") -> InputType.SHELL_COMMAND
            classify("explain @app.py") -> InputType.FILE_REFERENCE
            classify("fix the bug") -> InputType.NATURAL_LANGUAGE
        """
        text = text.strip()

        if not text:
            return InputType.NATURAL_LANGUAGE

        # Check for slash command (starts with /)
        if text.startswith("/"):
            return InputType.SLASH_COMMAND

        # Check for shell command (starts with !)
        if text.startswith("!"):
            return InputType.SHELL_COMMAND

        # Check for file reference (contains @)
        if "@" in text and self.AT_PATTERN.search(text):
            return InputType.FILE_REFERENCE

        # Default to natural language
        return InputType.NATURAL_LANGUAGE

    def preprocess(self, text: str) -> ProcessedInput:
        """
        Preprocess input text with classification and expansion

        Classifies the input and optionally expands file references.

        Args:
            text: Raw user input

        Returns:
            ProcessedInput with classification and processed text

        Example:
            result = processor.preprocess("explain @utils.py")
            # ProcessedInput(
            #     original="explain @utils.py",
            #     processed="explain src/utils.py",
            #     input_type=InputType.FILE_REFERENCE,
            #     file_references=["src/utils.py"],
            # )
        """
        original = text
        text = text.strip()
        input_type = self.classify(text)

        # Initialize result
        processed = text
        file_references: list[str] = []
        metadata: dict = {}

        # Handle based on type
        if input_type == InputType.SLASH_COMMAND:
            # Parse command name and arguments
            metadata = self._parse_slash_command(text)
            processed = text

        elif input_type == InputType.SHELL_COMMAND:
            # Extract shell command
            metadata = {"command": text[1:].strip()}
            processed = text

        elif input_type == InputType.FILE_REFERENCE:
            # Detect and optionally expand @references
            references = self._detect_references(text)
            file_references = [ref["query"] for ref in references]

            if self.expand_references:
                processed, file_references = self._expand_references(text, references)
                metadata["expanded"] = True
            else:
                metadata["expanded"] = False

            metadata["reference_count"] = len(file_references)

        # Natural language needs no special processing

        return ProcessedInput(
            original=original,
            processed=processed,
            input_type=input_type,
            file_references=file_references,
            metadata=metadata,
        )

    def _parse_slash_command(self, text: str) -> dict:
        """
        Parse slash command into name and arguments

        Args:
            text: Slash command text (e.g., "/agent plan")

        Returns:
            Dict with command_name and arguments
        """
        # Remove leading slash
        text = text[1:].strip()

        # Split into command and arguments
        parts = text.split(None, 1)
        command_name = parts[0].lower() if parts else ""
        arguments = parts[1] if len(parts) > 1 else ""

        return {
            "command_name": command_name,
            "arguments": arguments,
        }

    def _detect_references(self, text: str) -> list[dict]:
        """
        Detect @filename references in text

        Args:
            text: Input text

        Returns:
            List of reference dicts with original, query, start, end
        """
        references = []

        for match in self.AT_PATTERN.finditer(text):
            # Group 1 is quoted, group 2 is unquoted
            query = match.group(1) or match.group(2)
            references.append(
                {
                    "original": match.group(0),
                    "query": query,
                    "start": match.start(),
                    "end": match.end(),
                }
            )

        return references

    def _expand_references(self, text: str, references: list[dict]) -> tuple[str, list[str]]:
        """
        Expand @references to actual file paths

        Args:
            text: Original text
            references: List of reference dicts

        Returns:
            Tuple of (expanded text, list of file paths)
        """
        if not references:
            return text, []

        file_paths = []
        result_text = text

        # Process in reverse order to maintain positions
        for ref in reversed(references):
            matches = self.fuzzy_search.search(ref["query"], limit=1)

            if matches:
                file_path = matches[0].path
                file_paths.insert(0, file_path)  # Insert at beginning due to reverse
                result_text = result_text[: ref["start"]] + file_path + result_text[ref["end"] :]
            else:
                # Keep original reference if no match found
                file_paths.insert(0, ref["query"])

        return result_text, file_paths

    def detect_file_references(self, text: str) -> list[str]:
        """
        Detect file references without expanding

        Convenience method for just getting referenced files.

        Args:
            text: Input text

        Returns:
            List of file queries (not expanded paths)
        """
        references = self._detect_references(text)
        return [ref["query"] for ref in references]

    def expand_file_references(
        self, text: str, insert_contents: bool = False
    ) -> tuple[str, list[str]]:
        """
        Expand file references in text

        Args:
            text: Input text with @references
            insert_contents: Insert file contents instead of paths

        Returns:
            Tuple of (expanded text, list of file paths)
        """
        result = self.fuzzy_search.expand_references(
            text,
            interactive=False,
            insert_contents=insert_contents,
        )
        return result.text, [ref.selected.path for ref in result.references if ref.selected]

    def is_empty(self, text: str) -> bool:
        """Check if input is empty or whitespace"""
        return not text or not text.strip()

    def is_slash_command(self, text: str) -> bool:
        """Check if text is a slash command"""
        return self.classify(text) == InputType.SLASH_COMMAND

    def is_shell_command(self, text: str) -> bool:
        """Check if text is a shell command"""
        return self.classify(text) == InputType.SHELL_COMMAND

    def is_file_reference(self, text: str) -> bool:
        """Check if text contains file references"""
        return self.classify(text) == InputType.FILE_REFERENCE

    def is_natural_language(self, text: str) -> bool:
        """Check if text is natural language"""
        return self.classify(text) == InputType.NATURAL_LANGUAGE

    def get_command_info(self, text: str) -> Optional[dict]:
        """
        Get command information for slash commands

        Args:
            text: Potential slash command

        Returns:
            Dict with command_name and arguments, or None
        """
        if not self.is_slash_command(text):
            return None
        return self._parse_slash_command(text)

    def get_shell_command(self, text: str) -> Optional[str]:
        """
        Extract shell command from !command syntax

        Args:
            text: Potential shell command

        Returns:
            Shell command string or None
        """
        if not self.is_shell_command(text):
            return None
        return text[1:].strip()
