"""Console applet for executing Python scripts with interactive I/O.

This module provides a console interface for running Python scripts within Winston,
capturing their output and providing interactive input handling. Scripts execute
in separate threads while maintaining integration with Winston's dialog system.

Key Features:
    - Executes Python scripts/modules using runpy
    - Captures stdout/stderr during script execution
    - Provides custom input() function that integrates with Winston UI
    - Auto-detects asyncio usage and uses appropriate event loop handling
    - Supports both synchronous and asynchronous script execution
    - Thread-safe communication via SimpleQueue
    - Control+C handling for script interruption

Architecture:
    - ConsoleDialog: Main dialog extending Applet and InspectionDialog
    - script_runner(): Executes synchronous scripts in separate thread
    - async_script_runner(): Executes asyncio scripts with dedicated event loop
    - Custom input() replacement routes user input through dialog UI

Usage:
    Launch console with a script file or Source object to execute automatically.
    The console displays script output and prompts for input as needed.
"""
from typing import Any
import runpy
import threading
import os
import asyncio
from queue import SimpleQueue
from concurrent.futures import ThreadPoolExecutor
from ..key_handler import KeyHandler
from ..elements import Element
from ..subdialogs import Applet
from ..form_elements import InputPromptElement
from ..inspection import InspectionDialog
from .editor import Source


def script_runner(script_name: str, dialog: 'ConsoleDialog') -> None:
    """Run a Python script with custom input handling."""
    def custom_input(prompt: str) -> str:
        print(prompt, end='', flush=True)
        dialog[-1].empty_value = prompt
        dialog.bottom("")  #get the cursor on to the input prompt line in preparation for the user to respond to the prompt for input
        dialog.input_to_q = True
        input_object = dialog.input_q.get()  # Blocks until input is available
        dialog.input_to_q = False
        if isinstance(input_object, type) and issubclass(input_object, KeyboardInterrupt):
            raise input_object('user generated exception')
        else:  #this is a line of input text
            print(input_object)
            return input_object
    dialog.name = f'console executing script module {script_name}'
    dialog.set_output_consumption(True)
    try:
        runpy.run_module(script_name, run_name='__main__', init_globals={'input': custom_input, 'dialog': dialog})
    except KeyboardInterrupt as k:
        print(k)
    dialog.set_output_consumption(False)
    dialog[-1].empty_value = 'Input Prompt'
    dialog.move(len(dialog) - 1, 0, False, silent=True)
    dialog.say_strings(f'{script_name} script completed execution')
    dialog.name = 'console'


def async_script_runner(script_name: str, dialog: 'ConsoleDialog') -> None:
    """
    Runs asyncio-based scripts in a separate thread with its own event loop
    to avoid conflicts with the main COM/SimpleQueue event handling
    """
    def run_async_in_thread() -> None:
        # Create new event loop for this thread
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        def custom_input(prompt: str) -> str:
            print(prompt, end='', flush=True)
            dialog[-1].empty_value = prompt
            dialog.bottom("")
            dialog.input_to_q = True
            input_object = dialog.input_q.get()
            dialog.input_to_q = False
            if isinstance(input_object, type) and issubclass(input_object, KeyboardInterrupt):
                raise input_object('user generated exception')
            else:
                print(input_object)
                return input_object
        try:
            # Set up globals for the script
            script_globals = {
                'input': custom_input,
                'dialog': dialog,
                '__name__': '__main__'
            }
            # Use runpy.run_module which handles the module loading correctly
            runpy.run_module(script_name, init_globals=script_globals, run_name='__main__')
        except KeyboardInterrupt as k:
            print(f"KeyboardInterrupt: {k}")
        except Exception as e:
            print(f"Exception in async script: {e}")
            import traceback
            traceback.print_exc()
        finally:
            loop.close()
    # Update dialog state
    dialog.name = f'console executing async script module {script_name}'
    dialog.set_output_consumption(True)
    # Run the async script in a separate thread
    executor = ThreadPoolExecutor(max_workers=1)
    future = executor.submit(run_async_in_thread)
    # Wait for completion (this will block, but on the dialog thread, not the main COM thread)
    try:
        future.result()  # This blocks until the async thread completes
    except Exception as e:
        print(f"Error in async script runner: {e}")
    finally:
        dialog.set_output_consumption(False)
        dialog[-1].empty_value = 'Input Prompt'
        dialog.move(len(dialog)-1, 0, False, silent=True)
        dialog.say_strings(f'{script_name} async script completed execution')
        dialog.name = 'console'
        executor.shutdown(wait=True)


class ConsoleDialog(Applet, InspectionDialog):  # type: ignore[misc]
    """Console dialog for running Python scripts with interactive input.

    Supports both synchronous and asynchronous (asyncio) scripts.
    Auto-detects asyncio usage and selects appropriate runner.
    """

    input_q: SimpleQueue[Any]
    input_to_q: bool
    runner: threading.Thread | None
    async_runner: threading.Thread | None

    def generate_content(self, filler: str | Source | None, element_type: Any = None) -> Any:  # type: ignore[override]
        self.input_q = SimpleQueue()
        self.input_to_q = False
        self.runner = None
        self.async_runner = None  # Track async runner separately
        yield InputPromptElement(self, name='Input Prompt', value="")
        if isinstance(filler, str):
            filename = filler
        elif isinstance(filler, Source):
            filename = filler.filename
        else:
            filename = None
        if filename:
            module = os.path.splitext(os.path.basename(filename))[0]
            # Auto-detect if script contains asyncio and use appropriate runner
            if self._script_uses_asyncio(filename):
                self.run_async_script(module)
            else:
                self.run_script(module)

    def _script_uses_asyncio(self, filename: str) -> bool:
        """Simple heuristic to detect if a script uses asyncio."""
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                content = f.read()
                # Look for common asyncio patterns
                asyncio_indicators = [
                    'import asyncio',
                    'from asyncio',
                    'async def',
                    'await ',
                    'asyncio.run(',
                    'ClaudeSDKClient',  # Specific to your Claude SDK
                    'claude_code_sdk'
                ]
                return any(indicator in content for indicator in asyncio_indicators)
        except Exception:
            return False

    @KeyHandler.hotkey("Alt+R")
    def command_run_script(self, modified_name: str) -> None:
        script = self.input_text(title='run script', prompt='enter script name without extension')
        if script in (False, '') or not isinstance(script, str):
            self.say_strings('run script cancelled')
        else:
            self.say_strings('please wait')
            self.run_script(script)

    @KeyHandler.hotkey("Alt+A")
    def command_run_async_script(self, modified_name: str) -> None:
        """New command to explicitly run asyncio-based scripts."""
        script = self.input_text(title='run async script', prompt='enter async script name without extension')
        if script in (False, '') or not isinstance(script, str):
            self.say_strings('async run script cancelled')
        else:
            self.say_strings('please wait for async script')
            self.run_async_script(script)

    def run_script(self, script: str) -> None:
        self.runner = threading.Thread(target=script_runner, args=(script, self))
        self.runner.daemon = True
        self.runner.start()

    def run_async_script(self, script: str) -> None:
        """Run asyncio-based scripts in a separate thread with dedicated event loop."""
        self.async_runner = threading.Thread(target=async_script_runner, args=(script, self))
        self.async_runner.daemon = True
        self.async_runner.start()

    def on_close(self, result: Any) -> bool:
        # Check both regular and async runners
        runners_alive = []
        if self.runner and self.runner.is_alive():
            runners_alive.append('script runner')
        if hasattr(self, 'async_runner') and self.async_runner and self.async_runner.is_alive():
            runners_alive.append('async script runner')
        if runners_alive:
            runner_list = ' and '.join(runners_alive)
            user_result = self.message(title=f'{runner_list} thread(s) still active. Dismiss console anyway?')
            if user_result=='Yes':
                return False
            else:
                self.say_current()
                return True  # Don't close
        else:
            return False