"""
FileManager V2 Scaffold - Main scaffold implementation.

Features:
- BaseXMLScaffold with FileBlock XMLComponents
- @operation decorated methods for file CRUD
- Plugin system with auto-discovery
- Context lifecycle hooks for automatic linting
- 6 error display modes
"""

from typing import List, Optional, Union, Dict, Any, TYPE_CHECKING
from datetime import datetime

from egregore.core.context_scaffolds.xml_base import BaseXMLScaffold
from egregore.core.context_scaffolds.decorators import operation
from egregore.core.context_scaffolds.data_types import StateOperatorResult
from egregore.core.tool_calling.tool_declaration import ToolDeclaration

from .models import FileManagerState, FileBlock
from .file_operations import FileOperations
from .error_display import ErrorFormatter, ErrorDisplayMode
from .plugins import PluginManager

if TYPE_CHECKING:
    from egregore.core.agent.hooks.execution_contexts import ContextExecContext


class FileManagerScaffold(BaseXMLScaffold):
    """
    FileManager V2 Scaffold with automatic linting via context hooks.

    Architecture:
    - Files are FileBlock XMLComponents
    - Plugins provide linting/formatting/analysis
    - Context hooks trigger automatic linting on file changes
    - ErrorFormatter provides 6 display modes
    """

    type: str = "file_manager"
    state: FileManagerState = FileManagerState()

    def __init__(self,
                 linters: Optional[List[Union[str, Dict[str, Dict]]]] = None,
                 plugins: Optional[List[Union[str, Dict[str, Dict]]]] = None,
                 error_display_mode: str = "both",
                 working_dir: Optional[str] = None,
                 **kwargs):
        """
        Initialize FileManager scaffold.

        Args:
            linters: List of linters to enable (strings or dicts with config)
                Examples:
                    ['pyright']                          # Simple
                    [{'pyright': {'timeout': 15}}]       # Configured
                    ['pyright', {'ruff': {'strict': True}}]  # Mixed
            plugins: List of plugins to enable (for future non-linter plugins)
            error_display_mode: Error display mode (inline, detailed, both, minimal, off, custom)
            working_dir: Working directory for relative paths
            **kwargs: Additional BaseXMLScaffold arguments
        """
        super().__init__(**kwargs)

        # Set working_dir in state if provided
        if working_dir:
            self.state.working_dir = working_dir

        # Initialize plugin system (use object.__setattr__ for non-Pydantic fields)
        object.__setattr__(self, 'plugin_manager', PluginManager(auto_discover=True))

        # Initialize error formatter
        self.state.error_display_mode = error_display_mode
        object.__setattr__(self, 'error_formatter', ErrorFormatter(mode=ErrorDisplayMode(error_display_mode)))

        # Initialize file operations
        object.__setattr__(self, 'file_ops', FileOperations(self))

        # Setup linters if provided
        if linters:
            self._setup_linters(linters)

        # Setup plugins if provided
        if plugins:
            self._setup_plugins(plugins)

    # =========================================================================
    # PHASE 1: CORE FILE OPERATIONS (@operation decorated)
    # =========================================================================

    @operation
    def read(self, path: str, lines: Optional[str] = None) -> StateOperatorResult:
        """
        Read file from filesystem into memory.

        Args:
            path: File path (absolute or relative)
            lines: Optional line range (e.g., "1-50")

        Returns:
            StateOperatorResult with success status and file info
        """
        return self.file_ops.read(path, lines)

    @operation
    def write(self, path: str, content: str) -> StateOperatorResult:
        """
        Write content to file (create or overwrite).

        Args:
            path: File path
            content: File content

        Returns:
            StateOperatorResult with success status
        """
        self._ensure_hooks_registered()
        return self.file_ops.write(path, content)

    @operation
    def edit(self, path: str, old_text: str, new_text: str) -> StateOperatorResult:
        """
        Search and replace in file with diff output.

        Args:
            path: File path
            old_text: Text to find
            new_text: Text to replace with

        Returns:
            StateOperatorResult with diff output
        """
        self._ensure_hooks_registered()
        return self.file_ops.edit(path, old_text, new_text)

    @operation
    def close(self, path: str) -> StateOperatorResult:
        """
        Close file and free memory.

        Args:
            path: File path

        Returns:
            StateOperatorResult with success status
        """
        return self.file_ops.close(path)

    # =========================================================================
    # PHASE 1: RENDERING
    # =========================================================================

    def render(self):
        """
        Render all open files as FileBlock components.

        Returns:
            List[FileBlock] or string message if no files open
        """
        # Set XML attributes
        self.xml.attrs['file_count'] = str(len(self.state.files))
        self.xml.attrs['max_files'] = str(self.state.max_files_open)

        if not self.state.files:
            return "[No files open]"

        # Return list of FileBlock components
        return list(self.state.files.values())

    # =========================================================================
    # PHASE 2: PLUGIN SETUP
    # =========================================================================

    def _setup_linters(self, linters: List[Union[str, Dict[str, Dict]]]):
        """
        Setup linter plugins from mixed string/dict list.

        Examples:
            linters=['pyright']                          # Simple: use defaults
            linters=[{'pyright': {'timeout': 15}}]       # Configured
            linters=['pyright', {'ruff': {'strict': True}}]  # Mixed

        Args:
            linters: List of linter specifications
        """
        for linter_spec in linters:
            if isinstance(linter_spec, str):
                # Simple string: 'pyright'
                self.plugin_manager.enable_plugin(linter_spec)

            elif isinstance(linter_spec, dict):
                # Dict with config: {'pyright': {'timeout': 15}}
                for linter_name, config in linter_spec.items():
                    # Reconfigure plugin if needed
                    if config:
                        self.plugin_manager.configure_plugin(linter_name, config)

                    # Enable it
                    self.plugin_manager.enable_plugin(linter_name)

    def _setup_plugins(self, plugins: List[Union[str, Dict[str, Dict]]]):
        """
        Setup non-linter plugins (formatters, analyzers, etc.).

        Args:
            plugins: List of plugin specifications
        """
        for plugin_spec in plugins:
            if isinstance(plugin_spec, str):
                self.plugin_manager.enable_plugin(plugin_spec)
            elif isinstance(plugin_spec, dict):
                for plugin_name, config in plugin_spec.items():
                    if config:
                        self.plugin_manager.configure_plugin(plugin_name, config)
                    self.plugin_manager.enable_plugin(plugin_name)

    # =========================================================================
    # PHASE 3: CONTEXT LIFECYCLE HOOKS (Automatic Linting)
    # =========================================================================

    def _on_file_updated(self, ctx: 'ContextExecContext'):
        """
        Context lifecycle hook - fires after ANY context operation.

        Automatically runs linting when FileBlock content changes.

        Args:
            ctx: Context execution context
        """
        # Only process FileBlock components
        if not isinstance(ctx.component, FileBlock):
            return

        # Only process if component was actually modified
        if ctx.operation_type not in ['insert', 'update']:
            return

        # Get the FileBlock
        file_block = ctx.component

        # Run linting on the content
        if self.plugin_manager and self.plugin_manager.enabled_plugins:
            lint_results = self.plugin_manager.lint_file(
                file_block.path,
                file_block.content
            )

            # Update FileBlock with lint results
            updated_block = file_block.model_copy(update={
                'lint_results': lint_results,
                'last_linted': datetime.now()
            })

            # Set scaffold reference for error formatting
            updated_block._scaffold = self

            # Update in state
            self.state.files[file_block.path] = updated_block

            # Update in context (prevents infinite loop - won't trigger hook again)
            ctx.context.pact_update(ctx.selector, updated_block, mode='replace')

    # =========================================================================
    # PHASE 5: CUSTOM TOOL FORMAT (Optional)
    # =========================================================================

    def custom_tool_format(self) -> List[ToolDeclaration]:
        """
        Custom tool groupings for FileManager.

        Returns:
            List of custom ToolDeclaration instances
        """
        def file_manager_tool(operation: str, path: str, content: Optional[str] = None,
                             lines: Optional[str] = None):
            """
            Manage file lifecycle operations - read, write, close files.

            OPERATIONS:
            • read - Load file content (path, optional lines="1-50")
            • write - Save content (path, content)
            • close - Free memory (path)

            Args:
                operation: Operation to perform (read, write, close)
                path: File path
                content: File content (for write)
                lines: Line range (for read)

            Returns:
                StateOperatorResult with operation status
            """
            if operation == "read":
                return self.read(path, lines)
            elif operation == "write":
                if content is None:
                    return StateOperatorResult(success=False, message="content required for write")
                return self.write(path, content)
            elif operation == "close":
                return self.close(path)
            else:
                return StateOperatorResult(success=False, message=f"Unknown operation: {operation}")

        def file_editor_tool(operation: str, path: str, old_text: Optional[str] = None,
                            new_text: Optional[str] = None):
            """
            Edit file content using search-and-replace.

            OPERATIONS:
            • edit - Find and replace (path, old_text, new_text)

            Args:
                operation: Operation to perform (edit)
                path: File path
                old_text: Text to find
                new_text: Text to replace with

            Returns:
                StateOperatorResult with diff output
            """
            if operation == "edit":
                if old_text is None or new_text is None:
                    return StateOperatorResult(success=False, message="old_text and new_text required for edit")
                return self.edit(path, old_text, new_text)
            else:
                return StateOperatorResult(success=False, message=f"Unknown operation: {operation}")

        return [
            ToolDeclaration.from_callable(file_manager_tool, name="file_manager"),
            ToolDeclaration.from_callable(file_editor_tool, name="file_editor")
        ]

    # =========================================================================
    # PHASE 6: LAZY HOOK REGISTRATION
    # =========================================================================

    def _ensure_hooks_registered(self):
        """Lazily register hooks when needed - called before operations that trigger linting."""
        if not getattr(self, '_hooks_registered', False) and self.agent and self.plugin_manager.enabled_plugins:
            self.agent.hooks.context.after_change(self._on_file_updated)
            object.__setattr__(self, '_hooks_registered', True)
