"""
obs_controls.py - Complete OBS WebSocket control implementation.
All features extracted from obs_websocket.py
"""

import json
import asyncio
import os
import base64
import hashlib
from typing import Optional, Dict, Any
from pathlib import Path
import websockets
import dotenv

# Load .env from script directory
env_path = Path(__file__).parent / ".env"
dotenv.load_dotenv(dotenv_path=env_path)


class OBSError(Exception):
    """Custom exception for OBS operations."""
    pass


class OBSWebSocketClient:
    """Complete async OBS WebSocket client with all v5+ features."""

    def __init__(self, host: str = None, port: int = None, password: str = None):
        self.host = host or os.getenv("OBS_HOST", "localhost")
        self.port = port or int(os.getenv("OBS_PORT", "4455"))
        self.password = password or os.getenv("OBS_PASSWORD", "")
        self.ws: Optional[websockets.WebSocketClientProtocol] = None
        self._message_id = 0
        self._connected = False

    @property
    def url(self) -> str:
        return f"ws://{self.host}:{self.port}"

    async def connect(self) -> bool:
        """Connect to OBS WebSocket server with authentication."""
        try:
            self.ws = await websockets.connect(self.url)
            hello = await self.ws.recv()
            hello_data = json.loads(hello)
            
            if hello_data.get("op") == 0:  # Hello
                auth_required = hello_data["d"]["authentication"].get("challenge") is not None
                
                identify_payload = {
                    "op": 1,
                    "d": {
                        "rpcVersion": 1,
                        "eventSubscriptions": 33
                    }
                }
                
                if auth_required and self.password:
                    challenge = hello_data["d"]["authentication"]["challenge"]
                    salt = hello_data["d"]["authentication"]["salt"]
                    
                    secret = base64.b64encode(
                        hashlib.sha256((self.password + salt).encode()).digest()
                    ).decode()
                    auth_response = base64.b64encode(
                        hashlib.sha256((secret + challenge).encode()).digest()
                    ).decode()
                    identify_payload["d"]["authentication"] = auth_response
                
                await self.ws.send(json.dumps(identify_payload))
                response = await self.ws.recv()
                response_data = json.loads(response)
                
                if response_data.get("op") == 2:  # Identified
                    self._connected = True
                    return True
                else:
                    raise OBSError(f"OBS auth failed: {response_data}")
            
            self._connected = True
            return True
            
        except Exception as e:
            raise OBSError(f"Failed to connect to OBS at {self.url}: {e}")

    async def disconnect(self):
        """Disconnect from OBS."""
        if self.ws:
            await self.ws.close()
            self.ws = None
        self._connected = False

    def is_connected(self) -> bool:
        return self._connected and self.ws is not None

    def _next_id(self) -> str:
        self._message_id += 1
        return str(self._message_id)

    async def send_request(self, request_type: str, request_data: Optional[Dict] = None) -> Dict[str, Any]:
        """Send a request to OBS and wait for response."""
        if not self.ws:
            raise OBSError("Not connected to OBS")

        payload = {
            "op": 6,
            "d": {
                "requestType": request_type,
                "requestId": self._next_id(),
                "requestData": request_data or {}
            }
        }

        await self.ws.send(json.dumps(payload))
        
        while True:
            response = await self.ws.recv()
            data = json.loads(response)
            
            if data.get("op") == 7:  # RequestResponse
                if data["d"]["requestId"] == payload["d"]["requestId"]:
                    if data["d"].get("requestStatus", {}).get("result") is False:
                        raise OBSError(
                            f"OBS request failed: {data['d'].get('requestStatus', {}).get('comment', 'Unknown error')}"
                        )
                    return data["d"].get("responseData", {})
            elif data.get("op") == 5:  # Event
                pass  # Ignore events

    # ==================== SCENE MANAGEMENT ====================

    async def get_scene_list(self) -> Dict[str, Any]:
        return await self.send_request("GetSceneList")

    async def set_current_scene(self, scene_name: str) -> Dict[str, Any]:
        return await self.send_request("SetCurrentProgramScene", {"sceneName": scene_name})

    async def create_scene(self, scene_name: str) -> Dict[str, Any]:
        return await self.send_request("CreateScene", {"sceneName": scene_name})

    async def remove_scene(self, scene_name: str) -> Dict[str, Any]:
        return await self.send_request("RemoveScene", {"sceneName": scene_name})

    # ==================== RECORDING CONTROL ====================

    async def start_recording(self) -> Dict[str, Any]:
        return await self.send_request("StartRecord")

    async def stop_recording(self) -> Dict[str, Any]:
        return await self.send_request("StopRecord")

    async def pause_recording(self) -> Dict[str, Any]:
        return await self.send_request("PauseRecord")

    async def resume_recording(self) -> Dict[str, Any]:
        return await self.send_request("ResumeRecord")

    async def get_recording_status(self) -> Dict[str, Any]:
        return await self.send_request("GetRecordStatus")

    # ==================== STREAMING CONTROL ====================

    async def start_streaming(self) -> Dict[str, Any]:
        return await self.send_request("StartStream")

    async def stop_streaming(self) -> Dict[str, Any]:
        return await self.send_request("StopStream")

    async def get_streaming_status(self) -> Dict[str, Any]:
        return await self.send_request("GetStreamStatus")

    async def toggle_streaming(self) -> Dict[str, Any]:
        return await self.send_request("ToggleStream")

    async def send_stream_caption(self, caption_text: str) -> Dict[str, Any]:
        return await self.send_request("SendStreamCaption", {"captionText": caption_text})

    # ==================== VIRTUAL CAMERA ====================

    async def start_virtual_cam(self) -> Dict[str, Any]:
        return await self.send_request("StartVirtualCam")

    async def stop_virtual_cam(self) -> Dict[str, Any]:
        return await self.send_request("StopVirtualCam")

    async def get_virtual_cam_status(self) -> Dict[str, Any]:
        return await self.send_request("GetVirtualCamStatus")

    async def toggle_virtual_cam(self) -> Dict[str, Any]:
        return await self.send_request("ToggleVirtualCam")

    # ==================== REPLAY BUFFER ====================

    async def start_replay_buffer(self) -> Dict[str, Any]:
        return await self.send_request("StartReplayBuffer")

    async def stop_replay_buffer(self) -> Dict[str, Any]:
        return await self.send_request("StopReplayBuffer")

    async def save_replay_buffer(self) -> Dict[str, Any]:
        return await self.send_request("SaveReplayBuffer")

    async def get_replay_buffer_status(self) -> Dict[str, Any]:
        return await self.send_request("GetReplayBufferStatus")

    async def toggle_replay_buffer(self) -> Dict[str, Any]:
        return await self.send_request("ToggleReplayBuffer")

    async def get_last_replay_buffer_replay(self) -> Dict[str, Any]:
        return await self.send_request("GetLastReplayBufferReplay")

    # ==================== STUDIO MODE ====================

    async def set_studio_mode_enabled(self, enabled: bool) -> Dict[str, Any]:
        return await self.send_request("SetStudioModeEnabled", {"studioModeEnabled": enabled})

    async def toggle_studio_mode(self) -> Dict[str, Any]:
        return await self.send_request("ToggleStudioMode")

    async def get_preview_scene(self) -> Dict[str, Any]:
        return await self.send_request("GetCurrentPreviewScene")

    async def set_preview_scene(self, scene_name: str) -> Dict[str, Any]:
        return await self.send_request("SetCurrentPreviewScene", {"sceneName": scene_name})

    async def trigger_studio_mode_transition(self) -> Dict[str, Any]:
        return await self.send_request("TriggerStudioModeTransition")

    # ==================== SOURCE MANAGEMENT ====================

    async def get_scene_item_list(self, scene_name: str) -> Dict[str, Any]:
        return await self.send_request("GetSceneItemList", {"sceneName": scene_name})

    async def get_input_list(self, input_kind: Optional[str] = None) -> Dict[str, Any]:
        data = {}
        if input_kind:
            data["inputKind"] = input_kind
        return await self.send_request("GetInputList", data)

    async def get_input_settings(self, input_name: str) -> Dict[str, Any]:
        return await self.send_request("GetInputSettings", {"inputName": input_name})

    async def set_input_settings(self, input_name: str, settings: Dict, overlay: bool = True) -> Dict[str, Any]:
        return await self.send_request("SetInputSettings", {
            "inputName": input_name,
            "inputSettings": settings,
            "overlay": overlay
        })

    async def get_scene_item_enabled(self, scene_name: str, scene_item_id: int) -> Dict[str, Any]:
        return await self.send_request("GetSceneItemEnabled", {
            "sceneName": scene_name,
            "sceneItemId": scene_item_id
        })

    async def set_scene_item_enabled(self, scene_name: str, scene_item_id: int, enabled: bool) -> Dict[str, Any]:
        return await self.send_request("SetSceneItemEnabled", {
            "sceneName": scene_name,
            "sceneItemId": scene_item_id,
            "sceneItemEnabled": enabled
        })

    async def toggle_scene_item_enabled(self, scene_name: str, scene_item_id: int) -> bool:
        current = await self.get_scene_item_enabled(scene_name, scene_item_id)
        new_state = not current.get("sceneItemEnabled", True)
        await self.set_scene_item_enabled(scene_name, scene_item_id, new_state)
        return new_state

    async def get_scene_item_transform(self, scene_name: str, scene_item_id: int) -> Dict[str, Any]:
        return await self.send_request("GetSceneItemTransform", {
            "sceneName": scene_name,
            "sceneItemId": scene_item_id
        })

    async def set_scene_item_transform(self, scene_name: str, scene_item_id: int, transform: Dict) -> Dict[str, Any]:
        return await self.send_request("SetSceneItemTransform", {
            "sceneName": scene_name,
            "sceneItemId": scene_item_id,
            "sceneItemTransform": transform
        })

    async def create_input(self, scene_name: str, input_name: str, input_kind: str, settings: Optional[Dict] = None) -> Dict[str, Any]:
        data = {
            "sceneName": scene_name,
            "inputName": input_name,
            "inputKind": input_kind
        }
        if settings:
            data["inputSettings"] = settings
        return await self.send_request("CreateInput", data)

    async def remove_input(self, input_name: str) -> Dict[str, Any]:
        return await self.send_request("RemoveInput", {"inputName": input_name})

    # ==================== AUDIO CONTROL ====================

    async def set_input_mute(self, input_name: str, muted: bool) -> Dict[str, Any]:
        return await self.send_request("SetInputMute", {"inputName": input_name, "inputMuted": muted})

    async def toggle_input_mute(self, input_name: str) -> Dict[str, Any]:
        return await self.send_request("ToggleInputMute", {"inputName": input_name})

    async def get_input_volume(self, input_name: str) -> Dict[str, Any]:
        return await self.send_request("GetInputVolume", {"inputName": input_name})

    async def set_input_volume(self, input_name: str, volume_db: Optional[float] = None, volume_mul: Optional[float] = None) -> Dict[str, Any]:
        data = {"inputName": input_name}
        if volume_db is not None:
            data["inputVolumeDb"] = volume_db
        if volume_mul is not None:
            data["inputVolumeMul"] = volume_mul
        return await self.send_request("SetInputVolume", data)

    # ==================== TRANSITIONS ====================

    async def get_scene_transition_list(self) -> Dict[str, Any]:
        return await self.send_request("GetSceneTransitionList")

    async def set_current_scene_transition(self, transition_name: str) -> Dict[str, Any]:
        return await self.send_request("SetCurrentSceneTransition", {"transitionName": transition_name})

    async def set_current_scene_transition_duration(self, duration_ms: int) -> Dict[str, Any]:
        return await self.send_request("SetCurrentSceneTransitionDuration", {"transitionDuration": duration_ms})

    async def get_input_kind_list(self) -> Dict[str, Any]:
        return await self.send_request("GetInputKindList")

    # ==================== PROFILE & COLLECTION ====================

    async def get_profile_list(self) -> Dict[str, Any]:
        return await self.send_request("GetProfileList")

    async def set_current_profile(self, profile_name: str) -> Dict[str, Any]:
        return await self.send_request("SetCurrentProfile", {"profileName": profile_name})

    async def get_scene_collection_list(self) -> Dict[str, Any]:
        return await self.send_request("GetSceneCollectionList")

    async def set_current_scene_collection(self, scene_collection_name: str) -> Dict[str, Any]:
        return await self.send_request("SetCurrentSceneCollection", {"sceneCollectionName": scene_collection_name})

    # ==================== HOTKEYS ====================

    async def get_hotkey_list(self) -> Dict[str, Any]:
        return await self.send_request("GetHotkeyList")

    async def trigger_hotkey_by_name(self, hotkey_name: str) -> Dict[str, Any]:
        return await self.send_request("TriggerHotkeyByName", {"hotkeyName": hotkey_name})

    # ==================== STATS & INFO ====================

    async def get_version(self) -> Dict[str, Any]:
        return await self.send_request("GetVersion")

    async def get_stats(self) -> Dict[str, Any]:
        return await self.send_request("GetStats")

    async def get_stream_service_settings(self) -> Dict[str, Any]:
        return await self.send_request("GetStreamServiceSettings")

    async def set_stream_service_settings(self, service_type: str, settings: Dict) -> Dict[str, Any]:
        return await self.send_request("SetStreamServiceSettings", {
            "streamServiceType": service_type,
            "streamServiceSettings": settings
        })

    # ==================== SCREENSHOT ====================

    async def get_source_screenshot(self, source_name: str, image_format: str = "png", width: Optional[int] = None, height: Optional[int] = None, quality: Optional[int] = None) -> Dict[str, Any]:
        data = {
            "sourceName": source_name,
            "imageFormat": image_format
        }
        if width is not None and width > 0:
            data["imageWidth"] = width
        if height is not None and height > 0:
            data["imageHeight"] = height
        if quality is not None and quality >= 0:
            data["imageCompressionQuality"] = quality
        return await self.send_request("GetSourceScreenshot", data)

    async def save_source_screenshot(self, source_name: str, file_path: str, image_format: str = "png", width: Optional[int] = None, height: Optional[int] = None, quality: Optional[int] = None) -> Dict[str, Any]:
        data = {
            "sourceName": source_name,
            "imageFilePath": file_path,
            "imageFormat": image_format
        }
        if width is not None and width > 0:
            data["imageWidth"] = width
        if height is not None and height > 0:
            data["imageHeight"] = height
        if quality is not None and quality >= 0:
            data["imageCompressionQuality"] = quality
        return await self.send_request("SaveSourceScreenshot", data)

    # ==================== SPECIAL SOURCES ====================

    async def get_special_inputs(self) -> Dict[str, Any]:
        return await self.send_request("GetSpecialInputs")

    # ==================== FILTERS ====================

    async def get_source_filter_list(self, source_name: str) -> Dict[str, Any]:
        return await self.send_request("GetSourceFilterList", {"sourceName": source_name})

    async def get_source_filter(self, source_name: str, filter_name: str) -> Dict[str, Any]:
        return await self.send_request("GetSourceFilter", {"sourceName": source_name, "filterName": filter_name})

    async def set_source_filter_enabled(self, source_name: str, filter_name: str, enabled: bool) -> Dict[str, Any]:
        return await self.send_request("SetSourceFilterEnabled", {
            "sourceName": source_name,
            "filterName": filter_name,
            "filterEnabled": enabled
        })

    async def create_source_filter(self, source_name: str, filter_name: str, filter_kind: str, settings: Optional[Dict] = None) -> Dict[str, Any]:
        data = {
            "sourceName": source_name,
            "filterName": filter_name,
            "filterKind": filter_kind
        }
        if settings:
            data["filterSettings"] = settings
        return await self.send_request("CreateSourceFilter", data)

    async def remove_source_filter(self, source_name: str, filter_name: str) -> Dict[str, Any]:
        return await self.send_request("RemoveSourceFilter", {"sourceName": source_name, "filterName": filter_name})

    # ==================== BROWSER SOURCE ====================

    async def press_input_properties_button(self, input_name: str, property_name: str) -> Dict[str, Any]:
        return await self.send_request("PressInputPropertiesButton", {
            "inputName": input_name,
            "propertyName": property_name
        })

    async def refresh_browser_source(self, source_name: str) -> Dict[str, Any]:
        return await self.press_input_properties_button(source_name, "refreshnocache")

    # ==================== MEDIA SOURCE ====================

    async def get_media_input_status(self, input_name: str) -> Dict[str, Any]:
        return await self.send_request("GetMediaInputStatus", {"inputName": input_name})

    async def trigger_media_input_action(self, input_name: str, action: str) -> Dict[str, Any]:
        return await self.send_request("TriggerMediaInputAction", {
            "inputName": input_name,
            "mediaAction": action
        })

    # ==================== HIGH-LEVEL HELPERS ====================

    async def get_full_status(self) -> Dict[str, Any]:
        """Get comprehensive OBS status."""
        try:
            version = await self.get_version()
            stats = await self.get_stats()
            scenes = await self.get_scene_list()
            record = await self.get_recording_status()
            stream = await self.get_streaming_status()
            virtual_cam = await self.get_virtual_cam_status()
            replay = await self.get_replay_buffer_status()

            return {
                "connected": True,
                "version": version.get("obsVersion", "unknown"),
                "platform": version.get("platform", "unknown"),
                "websocket_version": version.get("obsWebSocketVersion", "unknown"),
                "current_scene": scenes.get("currentProgramSceneName", ""),
                "scenes": [s.get("sceneName", "") for s in scenes.get("scenes", [])],
                "recording": {
                    "active": record.get("outputActive", False),
                    "paused": record.get("outputPaused", False),
                    "timecode": record.get("outputTimecode", "")
                },
                "streaming": {
                    "active": stream.get("outputActive", False),
                    "reconnecting": stream.get("outputReconnecting", False),
                    "timecode": stream.get("outputTimecode", "")
                },
                "virtual_cam": {
                    "active": virtual_cam.get("outputActive", False)
                },
                "replay_buffer": {
                    "active": replay.get("outputActive", False)
                },
                "stats": {
                    "fps": stats.get("activeFps", 0),
                    "render_time_ms": stats.get("averageFrameRenderTime", 0),
                    "cpu_usage": stats.get("cpuUsage", 0),
                    "memory_usage": stats.get("memoryUsage", 0),
                    "output_skipped_frames": stats.get("outputSkippedFrames", 0),
                    "output_total_frames": stats.get("outputTotalFrames", 0)
                }
            }
        except Exception as e:
            return {"connected": False, "error": str(e)}

    async def find_scene_item_by_name(self, scene_name: str, source_name: str) -> Optional[int]:
        """Find scene item ID by source name. Returns None if not found."""
        items = await self.get_scene_item_list(scene_name)
        for item in items.get("sceneItems", []):
            if item.get("sourceName") == source_name:
                return item.get("sceneItemId")
        return None

    async def set_source_visibility(self, scene_name: str, source_name: str, visible: bool) -> bool:
        """Set source visibility by name. Returns True if successful."""
        item_id = await self.find_scene_item_by_name(scene_name, source_name)
        if item_id is None:
            return False
        await self.set_scene_item_enabled(scene_name, item_id, visible)
        return True

    # ==================== AUTO-RESIZE HELPERS ====================

    async def fit_source_to_canvas(
        self,
        scene_name: str,
        source_name: str,
        canvas_width: int = 1920,
        canvas_height: int = 1080,
        maintain_aspect_ratio: bool = True
    ) -> Dict[str, Any]:
        """Auto-fit a source to fill the canvas."""
        item_id = await self.find_scene_item_by_name(scene_name, source_name)
        if item_id is None:
            raise OBSError(f"Source '{source_name}' not found in scene '{scene_name}'")

        bounds_type = "OBS_BOUNDS_SCALE_INNER" if maintain_aspect_ratio else "OBS_BOUNDS_STRETCH"

        transform = {
            "positionX": 0.0,
            "positionY": 0.0,
            "boundsType": bounds_type,
            "boundsWidth": float(canvas_width),
            "boundsHeight": float(canvas_height),
            "boundsAlignment": 0
        }
        return await self.set_scene_item_transform(scene_name, item_id, transform)

    async def resize_source_percent(
        self,
        scene_name: str,
        source_name: str,
        percent: float
    ) -> Dict[str, Any]:
        """Resize a source to a percentage of its natural size."""
        item_id = await self.find_scene_item_by_name(scene_name, source_name)
        if item_id is None:
            raise OBSError(f"Source '{source_name}' not found in scene '{scene_name}'")

        scale = percent / 100.0
        transform = {
            "boundsType": "OBS_BOUNDS_NONE",
            "scaleX": scale,
            "scaleY": scale,
        }
        return await self.set_scene_item_transform(scene_name, item_id, transform)

    async def __aenter__(self):
        await self.connect()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.disconnect()


# ==================== COMMAND LINE INTERFACE ====================

async def execute_command(command: str) -> Dict[str, Any]:
    """Parse and execute an OBS command."""
    client = OBSWebSocketClient()
    parts = command.strip().split(maxsplit=1)
    cmd = parts[0].lower()
    args = parts[1] if len(parts) > 1 else ""
    
    try:
        await client.connect()
        
        result = {"success": True, "data": None}
        
        # Scene commands
        if cmd == "scene":
            scene_parts = args.split(maxsplit=1)
            if scene_parts[0] == "list":
                result["data"] = await client.get_scene_list()
            elif scene_parts[0] == "switch":
                result["data"] = await client.set_current_scene(scene_parts[1])
            elif scene_parts[0] == "create":
                result["data"] = await client.create_scene(scene_parts[1])
            elif scene_parts[0] == "remove":
                result["data"] = await client.remove_scene(scene_parts[1])
        
        # Recording commands
        elif cmd == "record":
            if args == "start":
                result["data"] = await client.start_recording()
            elif args == "stop":
                result["data"] = await client.stop_recording()
            elif args == "pause":
                result["data"] = await client.pause_recording()
            elif args == "resume":
                result["data"] = await client.resume_recording()
            elif args == "status":
                result["data"] = await client.get_recording_status()
        
        # Streaming commands
        elif cmd == "stream":
            if args == "start":
                result["data"] = await client.start_streaming()
            elif args == "stop":
                result["data"] = await client.stop_streaming()
            elif args == "status":
                result["data"] = await client.get_streaming_status()
            elif args == "toggle":
                result["data"] = await client.toggle_streaming()
            elif args.startswith("caption"):
                text = args.replace("caption ", "", 1)
                result["data"] = await client.send_stream_caption(text)
        
        # Virtual camera commands
        elif cmd == "virtualcam":
            if args == "start":
                result["data"] = await client.start_virtual_cam()
            elif args == "stop":
                result["data"] = await client.stop_virtual_cam()
            elif args == "status":
                result["data"] = await client.get_virtual_cam_status()
            elif args == "toggle":
                result["data"] = await client.toggle_virtual_cam()
        
        # Replay buffer commands
        elif cmd == "replay":
            if args == "start":
                result["data"] = await client.start_replay_buffer()
            elif args == "stop":
                result["data"] = await client.stop_replay_buffer()
            elif args == "save":
                result["data"] = await client.save_replay_buffer()
            elif args == "status":
                result["data"] = await client.get_replay_buffer_status()
            elif args == "toggle":
                result["data"] = await client.toggle_replay_buffer()
        
        # Studio mode commands
        elif cmd == "studio":
            if args == "enable":
                result["data"] = await client.set_studio_mode_enabled(True)
            elif args == "disable":
                result["data"] = await client.set_studio_mode_enabled(False)
            elif args == "toggle":
                result["data"] = await client.toggle_studio_mode()
            elif args == "transition":
                result["data"] = await client.trigger_studio_mode_transition()
            elif args.startswith("preview"):
                scene = args.replace("preview ", "", 1)
                result["data"] = await client.set_preview_scene(scene)
        
        # Audio commands
        elif cmd == "audio":
            audio_parts = args.split()
            if audio_parts[0] == "mute":
                result["data"] = await client.set_input_mute(audio_parts[1], True)
            elif audio_parts[0] == "unmute":
                result["data"] = await client.set_input_mute(audio_parts[1], False)
            elif audio_parts[0] == "toggle":
                result["data"] = await client.toggle_input_mute(audio_parts[1])
            elif audio_parts[0] == "volume":
                result["data"] = await client.set_input_volume(audio_parts[1], float(audio_parts[2]))
        
        # Transition commands
        elif cmd == "transition":
            trans_parts = args.split()
            if trans_parts[0] == "list":
                result["data"] = await client.get_scene_transition_list()
            elif trans_parts[0] == "set":
                result["data"] = await client.set_current_scene_transition(trans_parts[1])
            elif trans_parts[0] == "duration":
                result["data"] = await client.set_current_scene_transition_duration(int(trans_parts[1]))
        
        # Status commands
        elif cmd == "status":
            result["data"] = await client.get_full_status()
        elif cmd == "version":
            result["data"] = await client.get_version()
        elif cmd == "stats":
            result["data"] = await client.get_stats()
        
        # Source commands
        elif cmd == "source":
            src_parts = args.split(maxsplit=2)
            if src_parts[0] == "list":
                result["data"] = await client.get_scene_item_list(src_parts[1])
            elif src_parts[0] == "toggle":
                await client.set_source_visibility(src_parts[1], src_parts[2], True)
                result["data"] = {"toggled": True}
            elif src_parts[0] == "create":
                result["data"] = await client.create_input(src_parts[1], src_parts[2], src_parts[3])
            elif src_parts[0] == "remove":
                result["data"] = await client.remove_input(src_parts[1])
        
        # Screenshot command
        elif cmd == "screenshot":
            screenshot_parts = args.split()
            result["data"] = await client.save_source_screenshot(screenshot_parts[0], screenshot_parts[1])
        
        # Filter commands
        elif cmd == "filter":
            filt_parts = args.split(maxsplit=2)
            if filt_parts[0] == "list":
                result["data"] = await client.get_source_filter_list(filt_parts[1])
            elif filt_parts[0] == "toggle":
                result["data"] = await client.set_source_filter_enabled(filt_parts[1], filt_parts[2], True)
        
        # Browser refresh
        elif cmd == "browser":
            browser_parts = args.split()
            result["data"] = await client.refresh_browser_source(browser_parts[1])
        
        # Media commands
        elif cmd == "media":
            media_parts = args.split()
            action = media_parts[0]
            result["data"] = await client.trigger_media_input_action(media_parts[1], action)
        
        else:
            result = {"success": False, "error": f"Unknown command: {cmd}"}
        
        return result
        
    except Exception as e:
        return {"success": False, "error": str(e)}
    finally:
        await client.disconnect()


if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        command = " ".join(sys.argv[1:])
        result = asyncio.run(execute_command(command))
        print(json.dumps(result, indent=2))