"""
Main ESPal module - integrates all components

This module provides:
- Main ESPal class that ties everything together
- Integration of device detection, monitoring, building
- Command-line interface with daemon-based architecture
"""

import os
import sys
import time
import signal
import shutil
import threading

from .device import DeviceDetector, list_esp_devices
from .monitor import InteractiveMonitor
from .builder import BuildRunner
from .configparser import detect_build_system
from .client import ESPalClient, DaemonNotRunning, print_data_message, print_build_output
from .protocol import DataMessage, MarkerMessage, BuildCompleteMessage


def _find_platformio() -> str:
    """
    Find the PlatformIO executable.

    Searches in order:
    1. PATH (via shutil.which)
    2. Common installation locations

    Returns:
        Path to platformio executable, or 'pio' if not found (will use PATH)
    """
    # Check if 'pio' is in PATH
    pio_path = shutil.which('pio')
    if pio_path:
        return pio_path

    # Check if 'platformio' is in PATH
    platformio_path = shutil.which('platformio')
    if platformio_path:
        return platformio_path

    # Check common installation locations
    common_paths = [
        os.path.expanduser('~/.platformio/penv/bin/platformio'),
        os.path.expanduser('~/.local/bin/platformio'),
        '/usr/local/bin/platformio',
        '/usr/bin/platformio',
    ]

    for path in common_paths:
        if os.path.isfile(path) and os.access(path, os.X_OK):
            return path

    # Fall back to 'pio' and let the system find it
    return 'pio'


def _get_log_directory() -> str:
    """Get the log directory path."""
    # Use XDG_STATE_HOME if available, otherwise ~/.local/state
    xdg_state = os.environ.get('XDG_STATE_HOME', os.path.expanduser('~/.local/state'))
    return os.path.join(xdg_state, 'esppal', 'logs')


class ESPal:
    """
    Main ESPal class that integrates all components
    """
    
    def __init__(self, project_dir=None, device=None, vid=None, pid=None):
        """
        Initialize ESPal

        Args:
            project_dir: Project directory path, defaults to current directory
            device: Specific device path to use instead of auto-detection
            vid: USB Vendor ID to filter by (hex string)
            pid: USB Product ID to filter by (hex string)
        """
        self.project_dir = project_dir or os.getcwd()
        self.specific_device = device
        self.vid = vid
        self.pid = pid

        # Detect build system
        self.build_system, self.config = detect_build_system(self.project_dir)

        # Initialize components
        self.device_detector = DeviceDetector(
            callback=self._device_state_changed,
            specific_device=device
        )

        self.current_device = None
        self.monitor = None
        self.builder = BuildRunner(exit_callback=self._build_completed)

        # State tracking
        self.is_monitoring = False
        self.is_building = False
        self.reset_pending = False
        self.exit_requested = False

        # Signal handlers
        signal.signal(signal.SIGINT, self._signal_handler)
        signal.signal(signal.SIGTERM, self._signal_handler)
    
    def _signal_handler(self, sig, frame):
        """
        Handle signals (e.g., Ctrl+C)
        """
        if sig in (signal.SIGINT, signal.SIGTERM):
            print("\nExiting...")
            self.exit_requested = True
            self.cleanup()
            sys.exit(0)
    
    def _device_state_changed(self, connected, device):
        """
        Handle device connection state changes
        
        Args:
            connected: Whether device is connected
            device: Device path
        """
        if connected:
            print(f"Device connected: {device}")
            
            # Check if this is a new device path for the same physical device
            if self.current_device != device:
                old_device = self.current_device
                self.current_device = device
                
                # If we were monitoring the old device, switch to the new one
                if self.is_monitoring and old_device:
                    print(f"Device path changed from {old_device} to {device}")
                    print("Reconnecting monitor to new device path...")
                    
                    # Remember current baudrate
                    current_baudrate = self.get_baudrate()
                    
                    # Stop current monitor
                    self.stop_monitor()
                    
                    # Start new monitor with the same baudrate
                    self.start_monitor(current_baudrate)
            else:
                self.current_device = device
            
            # Auto-restart monitoring if needed
            if self.reset_pending and not self.is_monitoring:
                print("Restarting monitor after reset...")
                self.reset_pending = False
                self.start_monitor()
        else:
            print(f"Device disconnected: {device}")
            if device == self.current_device:
                # Try to find if the device reconnected with a different path
                new_device = self.device_detector.find_device()
                
                if new_device and new_device != device:
                    # Device path changed, handle it
                    print(f"Device appears to have reconnected as {new_device}")
                    self.current_device = new_device
                    
                    # If we were monitoring, reconnect to the new path
                    if self.is_monitoring:
                        print("Reconnecting monitor to new device path...")
                        current_baudrate = self.get_baudrate()
                        self.stop_monitor()
                        self.start_monitor(current_baudrate)
                else:
                    # Device truly disconnected
                    self.current_device = None
                    
                    # Stop monitoring if running
                    if self.is_monitoring:
                        print("Stopping monitor due to device disconnect...")
                        self.stop_monitor()
                        # Mark as reset pending to restart later
                        self.reset_pending = True
    
    def _build_completed(self, exit_code):
        """
        Handle build completion
        
        Args:
            exit_code: Build exit code
        """
        self.is_building = False
        
        # If we stopped monitoring for a build/upload, restart it
        if self.reset_pending and not self.is_monitoring:
            # Wait a moment for device to reconnect if necessary
            time.sleep(1)
            self.reset_pending = False
            
            # Find device again
            if not self.current_device:
                self.current_device = self.device_detector.find_device()
            
            if self.current_device:
                self.start_monitor()
            else:
                print("Waiting for device to reconnect...")
                self.device_detector.start_monitoring()
    
    def detect_device(self):
        """
        Detect ESP32 device

        Priority order:
        1. Specific device path (--device)
        2. VID/PID from command line (--vid/--pid or --vidpid)
        3. VID/PID from config (upload_vidpid in platformio.ini)
        4. upload_port from config
        5. Auto-detection

        Returns:
            str: Device path if found, None otherwise
        """
        # 1. First try specified device if any
        if self.specific_device and os.path.exists(self.specific_device):
            self.current_device = self.specific_device
            return self.specific_device

        # 2. Try VID/PID from command line
        if self.vid and self.pid:
            device = self.device_detector.find_device(vid=self.vid, pid=self.pid)
            if device:
                self.current_device = device
                return device

        # 3. Try VID/PID from config
        if self.config:
            env = self.config.get_default_environment()
            if env:
                vid, pid = self.config.get_vid_pid(env)
                if vid and pid:
                    device = self.device_detector.find_device(vid=vid, pid=pid)
                    if device:
                        self.current_device = device
                        return device

                # 4. Try upload_port from config
                device_from_config = self.config.get_upload_port(env)
                if device_from_config and os.path.exists(device_from_config):
                    self.current_device = device_from_config
                    return device_from_config

        # 5. Finally try auto-detection
        self.current_device = self.device_detector.find_device()
        return self.current_device
    
    def get_baudrate(self):
        """
        Get baudrate for the current device
        
        Returns:
            int: Baudrate value
        """
        if self.config:
            env = self.config.get_default_environment()
            if env:
                return self.config.get_baudrate(env)
        
        # Default baudrate
        return 115200
    
    def start_monitor(self, baudrate=None):
        """
        Start serial monitoring
        
        Args:
            baudrate: Serial baudrate, if None, auto-detect from config
            
        Returns:
            bool: True if successfully started, False otherwise
        """
        if self.is_monitoring:
            print("Already monitoring.")
            return True
        
        # Make sure we have a device
        if not self.current_device:
            if not self.detect_device():
                print("No device found to monitor.")
                return False
        
        # Use provided baudrate or get from config
        if baudrate is None:
            baudrate = self.get_baudrate()
        
        print(f"Starting monitor on {self.current_device} at {baudrate} baud")
        
        # Create and start the monitor
        self.monitor = InteractiveMonitor(
            self.current_device, 
            baudrate=baudrate,
            exit_callback=self._monitor_exited
        )
        
        # Register ESPal-specific commands
        self.monitor.register_command("build", self._cmd_build, "Build the project")
        self.monitor.register_command("upload", self._cmd_upload, "Upload firmware to the device")
        self.monitor.register_command("clean", self._cmd_clean, "Clean the project")
        self.monitor.register_command("reset", self._cmd_reset, "Reset the device")
        self.monitor.register_command("baudrate", self._cmd_baudrate, "Change monitor baudrate")
        self.monitor.register_command("quit", self._cmd_quit, "Cleanly exit ESPal")
        
        # Register shortcut for build (Ctrl+B)
        self.monitor.register_shortcut("^B", self._shortcut_ctrl_b, "Build the project")
        
        # Start monitoring
        if self.monitor.start():
            self.is_monitoring = True
            return True
        else:
            print("Failed to start monitor.")
            self.monitor = None
            return False
    
    def stop_monitor(self):
        """
        Stop serial monitoring
        
        Returns:
            bool: True if successfully stopped, False otherwise
        """
        if not self.is_monitoring or not self.monitor:
            return False
        
        self.monitor.stop()
        self.monitor = None
        self.is_monitoring = False
        return True
    
    def _monitor_exited(self):
        """
        Handle monitor exit
        """
        self.monitor = None
        self.is_monitoring = False
    
    def run_build_command(self, command_type, command=None):
        """
        Run a build-related command
        
        Args:
            command_type: Type of command (build, upload, clean)
            command: Command to run, if None, generate from build system
            
        Returns:
            bool: True if command started successfully
        """
        # Stop monitoring if running (will restart after build)
        was_monitoring = self.is_monitoring
        if was_monitoring:
            print("Pausing monitor for build...")
            self.stop_monitor()
            self.reset_pending = True
        
        if not command and self.build_system:
            # Generate command based on build system
            if self.build_system == "platformio":
                env = self.config.get_default_environment()
                env_arg = f"-e {env}" if env else ""
                
                pio = _find_platformio()
                if command_type == "build":
                    command = f"{pio} run {env_arg}"
                elif command_type == "upload":
                    command = f"{pio} run {env_arg} --target upload"
                elif command_type == "clean":
                    command = f"{pio} run {env_arg} --target clean"
            
            elif self.build_system == "arduino":
                if self.config:
                    board = self.config.get_default_environment()
                    if not board:
                        print("No board specified in Arduino config")
                        return False
                    
                    if command_type == "build":
                        command = f"arduino-cli compile --fqbn {board} ."
                    elif command_type == "upload":
                        port = self.current_device or ""
                        command = f"arduino-cli upload --fqbn {board} --port {port} ."
                    elif command_type == "clean":
                        command = f"arduino-cli compile --clean --fqbn {board} ."
            
            elif self.build_system == "espidf":
                if command_type == "build":
                    command = "idf.py build"
                elif command_type == "upload":
                    port = f"-p {self.current_device}" if self.current_device else ""
                    command = f"idf.py {port} flash"
                elif command_type == "clean":
                    command = "idf.py clean"
        
        if not command:
            print(f"No {command_type} command specified or build system detected")
            return False
        
        # Define output callback to display in console
        def output_callback(data, is_stderr=False):
            if is_stderr:
                sys.stderr.write(data)
            else:
                sys.stdout.write(data)
        
        # Run the command
        print(f"Running {command_type} command: {command}")
        
        if command_type == "build":
            result = self.builder.build(command, output_callback, cwd=self.project_dir)
        elif command_type == "upload":
            result = self.builder.upload(command, output_callback, cwd=self.project_dir)
        elif command_type == "clean":
            result = self.builder.clean(command, output_callback, cwd=self.project_dir)
        else:
            print(f"Unknown command type: {command_type}")
            return False
        
        if result:
            self.is_building = True
            return True
        else:
            print(f"Failed to start {command_type} command")
            
            # If we stopped monitoring, but failed to start build, restart it
            if was_monitoring and self.reset_pending:
                print("Restarting monitor...")
                self.reset_pending = False
                self.start_monitor()
            
            return False
    
    # Command handlers for interactive mode
    def _cmd_build(self, args):
        """Build command handler"""
        if self.is_building:
            print("A build is already in progress.")
            return
        
        command = args.strip() if args.strip() else None
        self.run_build_command("build", command)
    
    def _cmd_upload(self, args):
        """Upload command handler"""
        if self.is_building:
            print("A build is already in progress.")
            return
        
        command = args.strip() if args.strip() else None
        self.run_build_command("upload", command)
    
    def _cmd_clean(self, args):
        """Clean command handler"""
        if self.is_building:
            print("A build is already in progress.")
            return
        
        command = args.strip() if args.strip() else None
        self.run_build_command("clean", command)
    
    def _cmd_reset(self, args):
        """Reset device command handler"""
        if self.is_monitoring and self.monitor:
            print("Resetting device...")
            self.stop_monitor()
            self.reset_pending = True
            
            # Toggle DTR/RTS lines to reset ESP32
            import serial
            try:
                with serial.Serial(self.current_device, 115200, timeout=0.1) as ser:
                    ser.dtr = False
                    ser.rts = False
                    time.sleep(0.1)
                    ser.dtr = True
                    ser.rts = True
                    time.sleep(0.1)
                    ser.dtr = False
                    ser.rts = False
            except serial.SerialException:
                print("Failed to reset device via serial.")
    
    def _cmd_baudrate(self, args):
        """Change baudrate command handler"""
        if not args.strip():
            print(f"Current baudrate: {self.get_baudrate()}")
            return
        
        try:
            baudrate = int(args.strip())
            if baudrate <= 0:
                print("Baudrate must be a positive number.")
                return
            
            # Restart monitor with new baudrate
            if self.is_monitoring:
                print(f"Changing baudrate to {baudrate}...")
                self.stop_monitor()
                self.start_monitor(baudrate)
            else:
                print(f"Baudrate set to {baudrate} for next monitor start.")
        except ValueError:
            print("Invalid baudrate. Must be a number.")
    
    def _cmd_quit(self, args):
        """Quit ESPal cleanly"""
        print("Exiting ESPal...")
        self.exit_requested = True
        self.cleanup()
        # Exit the monitor mode
        if self.is_monitoring and self.monitor:
            self.stop_monitor()
        # Exit the program with code 0 (success)
        sys.exit(0)
    
    # Shortcut handlers for interactive mode
    def _shortcut_ctrl_b(self):
        """Ctrl+B shortcut handler"""
        if not self.is_building:
            print("\nBuilding project (Ctrl+B pressed)...")
            self.run_build_command("build")
    
    def cleanup(self):
        """Clean up resources"""
        # Stop monitoring
        if self.is_monitoring:
            self.stop_monitor()
        
        # Stop building
        if self.is_building:
            self.builder.terminate()
            self.is_building = False
        
        # Stop device detection
        self.device_detector.stop_monitoring()
    
    def wait_for_completion(self):
        """
        Wait for all operations to complete
        """
        try:
            while not self.exit_requested:
                if self.is_monitoring or self.is_building:
                    time.sleep(0.1)
                else:
                    break
        except KeyboardInterrupt:
            print("\nExiting...")
            self.cleanup()


def _parse_vidpid(args):
    """Parse VID/PID from arguments."""
    vid = getattr(args, 'vid', None)
    pid = getattr(args, 'pid', None)

    if hasattr(args, 'vidpid') and args.vidpid:
        if ':' in args.vidpid:
            parts = args.vidpid.split(':')
            if len(parts) == 2:
                vid = parts[0].strip()
                pid = parts[1].strip()

    return vid, pid


def _handle_status_command(args):
    """Handle the status command."""
    vid, pid = _parse_vidpid(args)
    device = getattr(args, 'device', None)

    client = ESPalClient(vid=vid, pid=pid, device=device, auto_spawn=False)

    try:
        status = client.status()
        if status is None:
            print("Daemon is not running.")
            return 1

        print("\033[1m╔═══════════════════════════════════════════════════════╗\033[0m")
        print("\033[1m║              \033[92mESPal Daemon Status\033[0m                       \033[1m║\033[0m")
        print("\033[1m╚═══════════════════════════════════════════════════════╝\033[0m")

        print(f"\n  Port:           {status.port or 'Not connected'}")
        print(f"  Port state:     {status.port_state}")
        print(f"  VID:PID:        {status.vid_pid or 'N/A'}")
        print(f"  Connected clients: {status.clients}")
        print(f"  Buffer:         {status.buffer_bytes / 1024:.1f} KB ({status.buffer_entries} entries)")
        print(f"  Uptime:         {status.uptime:.1f}s")
        print(f"  Idle timeout:   {status.idle_timeout:.1f}s remaining")
        print()
        return 0

    except DaemonNotRunning:
        print("Daemon is not running.")
        return 1
    finally:
        client.disconnect()


def _handle_daemon_command(args):
    """Handle daemon subcommands."""
    vid, pid = _parse_vidpid(args)
    device = getattr(args, 'device', None)

    if not args.daemon_command:
        print("Usage: esppal daemon <stop|status>")
        return 1

    client = ESPalClient(vid=vid, pid=pid, device=device, auto_spawn=False)

    try:
        if args.daemon_command == 'stop':
            if client.shutdown():
                print("Daemon shutdown requested.")
                return 0
            else:
                print("Failed to send shutdown request (daemon may not be running).")
                return 1

        elif args.daemon_command == 'status':
            # Delegate to status handler
            return _handle_status_command(args)

    except DaemonNotRunning:
        print("Daemon is not running.")
        return 1
    finally:
        client.disconnect()

    return 0


def _run_daemon_monitor(args, vid, pid, device):
    """Run monitor using the daemon client."""
    # Determine subscription mode
    mode = "live"
    marker = None
    lines = None

    if getattr(args, 'since_upload', False) or getattr(args, 'since_release', False):
        mode = "since"
        marker = "upload_complete"  # Use upload_complete marker
    elif getattr(args, 'tail', None):
        mode = "tail"
        lines = args.tail
    elif getattr(args, 'replay', False):
        mode = "replay"

    # Create client
    client = ESPalClient(vid=vid, pid=pid, device=device, auto_spawn=True)

    # Track if we're running
    running = True

    def on_disconnect():
        nonlocal running
        running = False
        print("\nDisconnected from daemon.")

    def on_data(msg):
        print_data_message(msg)

    def on_marker(msg):
        # Print markers as comments in the output
        print(f"\n--- {msg.name} ---\n")

    try:
        print(f"Connecting to daemon (mode={mode})...")

        if not client.subscribe(
            mode=mode,
            marker=marker,
            lines=lines,
            on_data=on_data,
            on_marker=on_marker,
            on_disconnect=on_disconnect
        ):
            print("Failed to subscribe to daemon.")
            return 1

        print(f"Subscribed to daemon. Press Ctrl+C to exit.")

        # Wait for disconnect or interrupt
        while running and client.connected:
            # For replay mode, exit after buffer is done
            if mode == "replay" and not client.connected:
                break
            time.sleep(0.1)

        return 0

    except DaemonNotRunning:
        print("Failed to connect to daemon. Is the device connected?")
        return 1
    except KeyboardInterrupt:
        print("\nExiting...")
        return 0
    finally:
        client.disconnect()


def main():
    """Main entry point for command-line interface"""
    import argparse
    import os
    
    # Get base directory for logs
    log_dir = _get_log_directory()
    build_log_dir = os.path.join(log_dir, "build")
    monitor_log_dir = os.path.join(log_dir, "monitor")
    
    # Custom formatter for better help formatting
    class CustomFormatter(argparse.HelpFormatter):
        def _format_action_invocation(self, action):
            if not action.option_strings:
                return super()._format_action_invocation(action)
            parts = []
            if action.nargs == 0:
                parts.extend(action.option_strings)
            else:
                default = action.dest.upper()
                args_string = self._format_args(action, default)
                for option_string in action.option_strings:
                    parts.append('%s %s' % (option_string, args_string))
            return ', '.join(parts)
    
    # Use RawDescriptionHelpFormatter to preserve formatting in the epilog
    parser = argparse.ArgumentParser(
        description='ESPal - ESP32 Development Tool for fluid simulations and other projects',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
Examples:
  esppal list                                      - List available ESP32 devices
  esppal discover                                  - Find your device's VID:PID (interactive)
  esppal monitor                                   - Monitor via daemon (auto-spawns if needed)
  esppal monitor --since-upload                    - Monitor from last upload marker
  esppal monitor --tail 100                        - Start with last 100 lines from buffer
  esppal monitor --replay                          - Replay buffer and exit
  esppal monitor --legacy                          - Use legacy direct serial mode
  esppal status                                    - Show daemon status
  esppal daemon stop                               - Stop the daemon
  esppal -d /dev/ttyUSB0 monitor                   - Monitor a specific device
  esppal -d /dev/ttyUSB0 -b 921600 monitor         - Monitor with specific baudrate
  esppal --vidpid 10c4:ea60 monitor                - Monitor device with VID:PID 10c4:ea60
  esppal --vid 10c4 --pid ea60 monitor             - Monitor device (alternate syntax)
  esppal build                                     - Build the current project
  esppal run                                       - Build and upload to device
  esppal platformio esp32dev build                 - Build using PlatformIO for esp32dev
  esppal platformio esp32dev upload                - Upload using PlatformIO
  esppal -p /path/to/project platformio esp32dev run - Build and upload to a specific project
  esppal arduino esp32:esp32:esp32c3 build         - Build using Arduino CLI
  esppal quit                                      - Terminate all ESPal instances

Run 'esppal' with no arguments for log directory locations.
''')
    
    # Global options
    parser.add_argument('--device', '-d', help='Serial device path')
    parser.add_argument('--baudrate', '-b', type=int, help='Serial baudrate')
    parser.add_argument('--project', '-p', help='Project directory path')
    parser.add_argument('--vid', help='USB Vendor ID (hex, e.g., 10c4)')
    parser.add_argument('--pid', help='USB Product ID (hex, e.g., ea60)')
    parser.add_argument('--vidpid', help='USB VID:PID (e.g., 10c4:ea60)')
    parser.add_argument('--help-all', action='store_true', help='Display detailed help information')
    
    # Command subparsers
    subparsers = parser.add_subparsers(dest='command', help='Command to run')
    
    # Monitor command
    monitor_parser = subparsers.add_parser('monitor', help='Start serial monitor')
    monitor_parser.add_argument('--since-upload', action='store_true',
                                help='Start from last upload marker')
    monitor_parser.add_argument('--since-release', action='store_true',
                                help='Start from last release marker (alias for --since-upload)')
    monitor_parser.add_argument('--tail', type=int, metavar='N',
                                help='Start from last N lines')
    monitor_parser.add_argument('--replay', action='store_true',
                                help='Replay buffer and exit (no live monitoring)')
    monitor_parser.add_argument('--legacy', action='store_true',
                                help='Use legacy direct serial mode (no daemon)')

    # Status command
    status_parser = subparsers.add_parser('status', help='Show daemon status')

    # Daemon command
    daemon_parser = subparsers.add_parser('daemon', help='Daemon control commands')
    daemon_subparsers = daemon_parser.add_subparsers(dest='daemon_command', help='Daemon subcommand')
    daemon_stop = daemon_subparsers.add_parser('stop', help='Stop the daemon')
    daemon_status = daemon_subparsers.add_parser('status', help='Show daemon status')

    # Build command
    build_parser = subparsers.add_parser('build', help='Build the project')
    build_parser.add_argument('build_args', nargs='?', help='Build command to run')
    
    # Upload command
    upload_parser = subparsers.add_parser('upload', help='Upload firmware to the device')
    upload_parser.add_argument('upload_args', nargs='?', help='Upload command to run')
    
    # Run command (build + upload)
    run_parser = subparsers.add_parser('run', help='Build and upload')
    run_parser.add_argument('run_args', nargs='?', help='Run command to run')
    
    # Clean command
    clean_parser = subparsers.add_parser('clean', help='Clean the project')
    clean_parser.add_argument('clean_args', nargs='?', help='Clean command to run')
    
    # List command
    list_parser = subparsers.add_parser('list', help='List available devices')

    # Discover command
    discover_parser = subparsers.add_parser('discover', help='Discover device VID/PID interactively')

    # Quit command
    quit_parser = subparsers.add_parser('quit', help='Cleanly exit any running ESPal instances')
    
    # Build system commands
    platformio_parser = subparsers.add_parser('platformio', help='PlatformIO commands')
    platformio_parser.add_argument('environment', help='PlatformIO environment')
    platformio_parser.add_argument('platformio_command', help='PlatformIO command (build, upload, run, clean, monitor)')
    
    arduino_parser = subparsers.add_parser('arduino', help='Arduino commands')
    arduino_parser.add_argument('board', help='Arduino board')
    arduino_parser.add_argument('arduino_command', help='Arduino command (build, upload, clean, monitor)')
    
    espidf_parser = subparsers.add_parser('espidf', help='ESP-IDF commands')
    espidf_parser.add_argument('target', nargs='?', help='ESP-IDF target chip')
    espidf_parser.add_argument('espidf_command', help='ESP-IDF command (build, flash, monitor, clean)')
    
    args = parser.parse_args()
    
    # Display welcome and help if no command provided or help-all is set
    if args.command is None or args.help_all:
        print("\033[1m╔═══════════════════════════════════════════════════════╗\033[0m")
        print("\033[1m║              \033[92mESPal - ESP32 Development Tool\033[0m            \033[1m║\033[0m")
        print("\033[1m╚═══════════════════════════════════════════════════════╝\033[0m")
        print("\nAvailable commands:")
        print("  \033[1mmonitor\033[0m     - Start serial monitor (uses daemon)")
        print("  \033[1mstatus\033[0m      - Show daemon status")
        print("  \033[1mdaemon\033[0m      - Daemon control (daemon stop, daemon status)")
        print("  \033[1mbuild\033[0m       - Build the project")
        print("  \033[1mupload\033[0m      - Upload firmware to the device")
        print("  \033[1mrun\033[0m         - Build and upload")
        print("  \033[1mclean\033[0m       - Clean the project")
        print("  \033[1mlist\033[0m        - List available ESP32 devices")
        print("  \033[1mdiscover\033[0m    - Discover device VID/PID interactively")
        print("  \033[1mquit\033[0m        - Cleanly exit any running ESPal instances")

        print("\nMonitor options:")
        print("  \033[1m--since-upload\033[0m  - Start from last upload marker")
        print("  \033[1m--tail N\033[0m        - Start with last N lines from buffer")
        print("  \033[1m--replay\033[0m        - Replay buffer and exit")
        print("  \033[1m--legacy\033[0m        - Use legacy direct serial mode")

        print("\nGlobal options:")
        print("  \033[1m--device, -d\033[0m    - Specify the serial device path")
        print("  \033[1m--baudrate, -b\033[0m  - Specify the serial baudrate")
        print("  \033[1m--project, -p\033[0m   - Specify the project directory")
        print("  \033[1m--vid\033[0m           - USB Vendor ID (hex, e.g., 10c4)")
        print("  \033[1m--pid\033[0m           - USB Product ID (hex, e.g., ea60)")
        print("  \033[1m--vidpid\033[0m        - USB VID:PID (e.g., 10c4:ea60)")

        print("\nLog directories:")
        print(f"  Build logs:    \033[94m{build_log_dir}\033[0m")
        print(f"  Monitor logs:  \033[94m{monitor_log_dir}\033[0m")

        print("\nExamples:")
        print("  \033[93mesppal list\033[0m                                      - List available ESP32 devices")
        print("  \033[93mesppal discover\033[0m                                  - Find your device's VID:PID (interactive)")
        print("  \033[93mesppal monitor\033[0m                                   - Monitor via daemon (auto-spawns)")
        print("  \033[93mesppal monitor --since-upload\033[0m                    - Monitor from last upload marker")
        print("  \033[93mesppal monitor --tail 100\033[0m                        - Start with last 100 lines")
        print("  \033[93mesppal status\033[0m                                    - Show daemon status")
        print("  \033[93mesppal daemon stop\033[0m                               - Stop the daemon")
        print("  \033[93mesppal -d /dev/ttyUSB0 monitor\033[0m                   - Monitor a specific device")
        print("  \033[93mesppal --vidpid 10c4:ea60 monitor\033[0m                - Monitor device with VID:PID 10c4:ea60")
        print("  \033[93mesppal build\033[0m                                     - Build the current project")
        print("  \033[93mesppal run\033[0m                                       - Build and upload to device")
        print("  \033[93mesppal platformio esp32dev build\033[0m                 - Build using PlatformIO for esp32dev")
        print("  \033[93mesppal quit\033[0m                                      - Terminate all ESPal instances")

        print("\nFor more detailed information, run: \033[93mesppal --help\033[0m")
        return 0
    
    # Handle list command separately since it doesn't need an ESPal instance
    if args.command == 'list':
        devices = list_esp_devices()
        if not devices:
            print("No ESP32 devices found.")
        else:
            print(f"Found {len(devices)} ESP32 device(s):")
            for i, device in enumerate(devices):
                print(f"{i+1}. {device['device']} - {device['description']} ({device['vendor']})")
        return 0

    # Handle discover command
    if args.command == 'discover':
        print("\033[1m╔═══════════════════════════════════════════════════════╗\033[0m")
        print("\033[1m║          \033[92mESPal USB Device Discovery\033[0m                 \033[1m║\033[0m")
        print("\033[1m╚═══════════════════════════════════════════════════════╝\033[0m")
        print("\nThis tool will help you find your ESP32 device's VID:PID.")
        print("\n\033[93mStep 1:\033[0m Disconnect your ESP32 device from USB")
        input("Press Enter when device is disconnected...")

        # Get list of devices before
        print("\nScanning USB ports...")
        before_devices = list_esp_devices()

        print("\n\033[93mStep 2:\033[0m Connect your ESP32 device to USB")
        input("Press Enter when device is connected...")

        # Get list of devices after
        print("\nScanning USB ports...")
        time.sleep(1)  # Give system time to enumerate device
        after_devices = list_esp_devices()

        # Find the new device
        before_paths = {d['device'] for d in before_devices}
        new_devices = [d for d in after_devices if d['device'] not in before_paths]

        if not new_devices:
            print("\n\033[91mNo new device detected!\033[0m")
            print("\nTroubleshooting:")
            print("  1. Make sure the USB cable is connected properly")
            print("  2. Try a different USB cable (some are power-only)")
            print("  3. Check if the device appears in 'lsusb'")
            print("  4. Try running 'esppal list' to see all devices")
            return 1

        if len(new_devices) > 1:
            print(f"\n\033[93mWarning: {len(new_devices)} new devices detected\033[0m")
            print("Showing the first one:\n")

        device = new_devices[0]
        print("\n\033[92m✓ Device discovered!\033[0m")
        print("\n" + "="*60)
        print(f"  Device Path:  {device['device']}")
        print(f"  Description:  {device['description']}")
        print(f"  Vendor:       {device['vendor']}")

        # Extract VID:PID from hardware ID
        if device.get('hardware_id'):
            import re
            match = re.search(r'VID:PID=([0-9A-Fa-f]{4}):([0-9A-Fa-f]{4})', device['hardware_id'])
            if match:
                vid = match.group(1).lower()
                pid = match.group(2).lower()
                print(f"  \033[1mVID:PID:      {vid}:{pid}\033[0m")
                print("="*60)

                print("\n\033[93mTo use this device with ESPal:\033[0m")
                print("\n1. Add to your platformio.ini:")
                print(f"\n\033[96m  [env:your_environment]")
                print(f"  platform = espressif32")
                print(f"  board = your_board")
                print(f"  upload_vidpid = {vid}:{pid}\033[0m")

                print("\n2. Then use ESPal normally:")
                print(f"\n\033[96m  esppal monitor\033[0m")
                print(f"\n  Or specify VID:PID on command line:")
                print(f"\n\033[96m  esppal --vidpid {vid}:{pid} monitor\033[0m\n")
                return 0

        print("="*60)
        print("\n\033[93mNote: Could not extract VID:PID from device info\033[0m")
        print("Try using 'lsusb' to find the VID:PID manually.\n")
        return 0

    # Handle quit command separately 
    if args.command == 'quit':
        print("Terminating all running ESPal processes...")
        try:
            # Try to find and terminate any running ESPal processes
            import subprocess
            import os
            
            # Find PIDs of ESPal processes (simple approach)
            try:
                # Use pgrep to find processes with "esppal" in their name
                result = subprocess.run(["pgrep", "-f", "esppal"], 
                                       stdout=subprocess.PIPE, 
                                       stderr=subprocess.PIPE,
                                       text=True)
                
                if result.returncode == 0 and result.stdout.strip():
                    pids = result.stdout.strip().split('\n')
                    own_pid = os.getpid()
                    
                    # Terminate each process except our own
                    for pid_str in pids:
                        try:
                            pid = int(pid_str)
                            if pid != own_pid:  # Don't kill ourselves
                                print(f"Terminating ESPal process: {pid}")
                                os.kill(pid, signal.SIGTERM)
                        except (ValueError, ProcessLookupError):
                            pass
                    
                    print("All ESPal processes terminated.")
                else:
                    print("No running ESPal processes found.")
            except FileNotFoundError:
                print("Could not find pgrep. Please terminate ESPal processes manually.")
                
        except Exception as e:
            print(f"Error terminating processes: {e}")

        return 0

    # Handle status command
    if args.command == 'status':
        return _handle_status_command(args)

    # Handle daemon command
    if args.command == 'daemon':
        return _handle_daemon_command(args)

    # Parse VID/PID arguments
    vid = args.vid
    pid = args.pid

    # If --vidpid is provided, split it into vid and pid
    if args.vidpid:
        if ':' in args.vidpid:
            parts = args.vidpid.split(':')
            if len(parts) == 2:
                vid = parts[0].strip()
                pid = parts[1].strip()

    # Create ESPal instance
    esppal = ESPal(project_dir=args.project, device=args.device, vid=vid, pid=pid)
    
    try:
        # Execute requested command
        if args.command == 'monitor':
            # Check for legacy mode
            if getattr(args, 'legacy', False):
                # Use legacy direct serial mode
                if not esppal.start_monitor(args.baudrate):
                    return 1

                # Wait for monitor to exit
                esppal.wait_for_completion()
            else:
                # Use daemon-based monitoring (default)
                esppal.cleanup()  # Don't need ESPal instance for daemon mode
                return _run_daemon_monitor(args, vid, pid, args.device)
        
        elif args.command == 'build':
            # Build project
            if not esppal.run_build_command('build', args.build_args):
                return 1
            
            # Wait for build to complete
            while esppal.is_building:
                time.sleep(0.1)
            
            return 0 if esppal.builder.get_exit_code() == 0 else 1
        
        elif args.command == 'upload':
            # Upload firmware
            if not esppal.run_build_command('upload', args.upload_args):
                return 1
            
            # Wait for upload to complete
            while esppal.is_building:
                time.sleep(0.1)
            
            return 0 if esppal.builder.get_exit_code() == 0 else 1
        
        elif args.command == 'run':
            # Build and upload
            # First build
            if not esppal.run_build_command('build', args.run_args):
                return 1
            
            # Wait for build to complete
            while esppal.is_building:
                time.sleep(0.1)
            
            # If build succeeded, upload
            if esppal.builder.get_exit_code() == 0:
                if not esppal.run_build_command('upload', args.run_args):
                    return 1
                
                # Wait for upload to complete
                while esppal.is_building:
                    time.sleep(0.1)
                
                return 0 if esppal.builder.get_exit_code() == 0 else 1
            else:
                return 1
        
        elif args.command == 'clean':
            # Clean project
            if not esppal.run_build_command('clean', args.clean_args):
                return 1
            
            # Wait for clean to complete
            while esppal.is_building:
                time.sleep(0.1)
            
            return 0 if esppal.builder.get_exit_code() == 0 else 1
        
        elif args.command == 'platformio':
            # PlatformIO commands
            env = args.environment
            cmd = args.platformio_command
            
            if cmd == 'monitor':
                # Just start the monitor
                if not esppal.start_monitor(args.baudrate):
                    return 1
                
                # Wait for monitor to exit
                esppal.wait_for_completion()
            else:
                # Convert to standard commands
                command_map = {
                    'build': 'build',
                    'upload': 'upload',
                    'run': 'run',
                    'clean': 'clean'
                }
                
                if cmd not in command_map:
                    print(f"Unknown PlatformIO command: {cmd}")
                    return 1
                
                # Execute the command
                pio = _find_platformio()
                command = f"{pio} run -e {env}"
                if cmd == 'build':
                    pass  # Default is build
                elif cmd == 'upload':
                    command += " --target upload"
                elif cmd == 'run':
                    command += " --target upload"
                elif cmd == 'clean':
                    command += " --target clean"
                
                if not esppal.run_build_command(command_map[cmd], command):
                    return 1
                
                # Wait for command to complete
                while esppal.is_building:
                    time.sleep(0.1)
                
                return 0 if esppal.builder.get_exit_code() == 0 else 1
        
        elif args.command == 'arduino':
            # Arduino commands
            board = args.board
            cmd = args.arduino_command
            
            if cmd == 'monitor':
                # Just start the monitor
                if not esppal.start_monitor(args.baudrate):
                    return 1
                
                # Wait for monitor to exit
                esppal.wait_for_completion()
            else:
                # Convert to standard commands
                command_map = {
                    'build': 'build',
                    'upload': 'upload',
                    'clean': 'clean'
                }
                
                if cmd not in command_map:
                    print(f"Unknown Arduino command: {cmd}")
                    return 1
                
                # Execute the command
                if cmd == 'build':
                    command = f"arduino-cli compile --fqbn {board} ."
                elif cmd == 'upload':
                    port = args.device or esppal.current_device or ""
                    port_arg = f"--port {port}" if port else ""
                    command = f"arduino-cli upload --fqbn {board} {port_arg} ."
                elif cmd == 'clean':
                    command = f"arduino-cli compile --clean --fqbn {board} ."
                
                if not esppal.run_build_command(command_map[cmd], command):
                    return 1
                
                # Wait for command to complete
                while esppal.is_building:
                    time.sleep(0.1)
                
                return 0 if esppal.builder.get_exit_code() == 0 else 1
        
        elif args.command == 'espidf':
            # ESP-IDF commands
            target = args.target
            cmd = args.espidf_command
            
            if cmd == 'monitor':
                # Just start the monitor
                if not esppal.start_monitor(args.baudrate):
                    return 1
                
                # Wait for monitor to exit
                esppal.wait_for_completion()
            else:
                # Convert to standard commands
                command_map = {
                    'build': 'build',
                    'flash': 'upload',
                    'clean': 'clean'
                }
                
                if cmd not in command_map:
                    print(f"Unknown ESP-IDF command: {cmd}")
                    return 1
                
                # Execute the command
                command = "idf.py"
                if target:
                    command += f" -C {target}"
                
                if cmd == 'build':
                    command += " build"
                elif cmd == 'flash':
                    port = args.device or esppal.current_device or ""
                    port_arg = f"-p {port}" if port else ""
                    command += f" {port_arg} flash"
                elif cmd == 'clean':
                    command += " clean"
                
                if not esppal.run_build_command(command_map[cmd], command):
                    return 1
                
                # Wait for command to complete
                while esppal.is_building:
                    time.sleep(0.1)
                
                return 0 if esppal.builder.get_exit_code() == 0 else 1
        
        return 0
    
    except KeyboardInterrupt:
        print("\nExiting...")
        esppal.cleanup()
        return 1
    
    finally:
        esppal.cleanup()


if __name__ == "__main__":
    sys.exit(main())
