from abc import ABC, abstractmethod
from typing import AsyncIterator, Optional
from .constants import CLIConstants
from .result import ParsedInput, StreamChunk, InputType


class Executor(ABC):
    @abstractmethod
    def can_execute(self, parsed: ParsedInput) -> bool:
        pass

    @abstractmethod
    async def execute(self, parsed: ParsedInput) -> AsyncIterator[StreamChunk]:
        pass


class CommandExecutor(Executor):
    def __init__(self):
        self._executors: list[Executor] = []

    def register(self, executor: Executor) -> None:
        self._executors.append(executor)

    def can_execute(self, parsed: ParsedInput) -> bool:
        for executor in self._executors:
            if executor.can_execute(parsed):
                return True
        return False

    async def execute(self, parsed: ParsedInput) -> AsyncIterator[StreamChunk]:
        for executor in self._executors:
            if executor.can_execute(parsed):
                async for chunk in executor.execute(parsed):
                    yield chunk
                return
        
        yield StreamChunk(content=f"Unknown command: {parsed.command_name}\n", is_error=True, is_final=True)


class Engine:
    def __init__(self):
        self._parser = None
        self._mode_manager = None
        self._client_adapter = None
        self._agent_adapter = None
        self._web_adapter = None
        self._register_adapters()

    def _get_parser(self):
        if self._parser is None:
            from diona.project.cli.engine.parser import get_input_parser
            self._parser = get_input_parser()
        return self._parser

    def _get_mode_manager(self):
        if self._mode_manager is None:
            from diona.project.cli.modes import get_mode_manager
            self._mode_manager = get_mode_manager()
        return self._mode_manager

    def _register_adapters(self):
        from diona.project.cli.engine.adapter import get_client_adapter, get_agent_adapter, get_web_adapter
        self._client_adapter = get_client_adapter()
        self._agent_adapter = get_agent_adapter()
        self._web_adapter = get_web_adapter()

    def set_mode(self, mode: str) -> bool:
        return self._get_mode_manager().set_mode(mode)

    def get_current_mode(self) -> str:
        mode = self._get_mode_manager().current_mode
        if mode:
            return mode.name
        return CLIConstants.MODE_CLIENT

    def get_prompt_prefix(self) -> str:
        """Get the prompt prefix based on current mode"""
        mode = self._get_mode_manager().current_mode
        if mode:
            return mode.prompt_prefix
        return "<diona@local>"

    def is_default_mode(self) -> bool:
        return self._get_mode_manager().is_default_mode

    async def execute(self, user_input: str) -> AsyncIterator[StreamChunk]:
        if user_input.startswith(CLIConstants.LOCAL_PREFIX):
            local_input = user_input[len(CLIConstants.LOCAL_PREFIX):].strip()
            async for chunk in self._execute_local(local_input):
                yield chunk
            return

        if user_input.startswith(CLIConstants.MODE_PREFIX):
            command = user_input[1:].strip()
            
            if command == CLIConstants.MODE_EXIT:
                self._get_mode_manager().set_mode(CLIConstants.MODE_CLIENT)
                yield StreamChunk(content="Exited to client mode.\n", is_final=True)
                return
            
            if command in (CLIConstants.MODE_AGENT, CLIConstants.MODE_WEB, CLIConstants.MODE_CLIENT):
                self._get_mode_manager().set_mode(command)
                yield StreamChunk(content=f"Switched to {command} mode.\n", is_final=True)
                return
            
            yield StreamChunk(content=f"Unknown mode: {command}\n", is_error=True, is_final=True)
            return

        current_mode = self.get_current_mode()

        if current_mode == CLIConstants.MODE_CLIENT:
            async for chunk in self._client_adapter.execute(user_input):
                yield chunk
            return

        if current_mode == CLIConstants.MODE_AGENT:
            async for chunk in self._execute_agent_mode(user_input):
                yield chunk
            return

        if current_mode == CLIConstants.MODE_WEB:
            async for chunk in self._execute_web_mode(user_input):
                yield chunk
            return

    async def _execute_agent_mode(self, user_input: str) -> AsyncIterator[StreamChunk]:
        if user_input.startswith("$exit"):
            self._get_mode_manager().set_mode(CLIConstants.MODE_CLIENT)
            yield StreamChunk(content="Exited to client mode.\n", is_final=True)
            return
        
        if user_input.startswith("$"):
            command = user_input[1:].strip()
            if command in (CLIConstants.MODE_AGENT, CLIConstants.MODE_WEB, CLIConstants.MODE_CLIENT):
                self._get_mode_manager().set_mode(command)
                yield StreamChunk(content=f"Switched to {command} mode.\n", is_final=True)
                return

        if user_input.startswith("/"):
            async for chunk in self._client_adapter.execute(user_input):
                yield chunk
            return

        async for chunk in self._agent_adapter.execute(user_input):
            yield chunk

    async def _execute_web_mode(self, user_input: str) -> AsyncIterator[StreamChunk]:
        if user_input.startswith("$exit"):
            yield StreamChunk(content="Use $client to exit web mode.\n", is_final=True)
            return
        
        if user_input.startswith("$"):
            command = user_input[1:].strip()
            if command in (CLIConstants.MODE_AGENT, CLIConstants.MODE_WEB, CLIConstants.MODE_CLIENT):
                self._get_mode_manager().set_mode(command)
                yield StreamChunk(content=f"Switched to {command} mode.\n", is_final=True)
                return
            yield StreamChunk(content=f"Unknown mode: {command}\n", is_error=True, is_final=True)
            return

        if user_input.startswith("/"):
            async for chunk in self._client_adapter.execute(user_input):
                yield chunk
            return

        async for chunk in self._web_adapter.execute(user_input):
            yield chunk

    async def _execute_local(self, local_input: str) -> AsyncIterator[StreamChunk]:
        parser = self._get_parser()
        parsed = parser.parse(local_input)
        
        from diona.project.cli.commands.executor import BuiltinExecutor, RegisteredExecutor, SystemExecutor
        executor = CommandExecutor()
        executor.register(BuiltinExecutor())
        executor.register(RegisteredExecutor())
        executor.register(SystemExecutor())
        
        async for chunk in executor.execute(parsed):
            yield chunk

    def execute_sync(self, user_input: str):
        import asyncio
        try:
            loop = asyncio.get_running_loop()
            loop.create_task(self._execute_and_print(user_input))
        except RuntimeError:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            try:
                loop.run_until_complete(self._execute_and_print(user_input))
            finally:
                loop.close()

    async def _execute_and_print(self, user_input: str):
        async for chunk in self.execute(user_input):
            if chunk.content:
                print(chunk.content, end="", flush=True)
            if chunk.is_final:
                print()


_engine: Optional[Engine] = None


def get_engine() -> Engine:
    global _engine
    if _engine is None:
        _engine = Engine()
    return _engine
