import sys
import os
from abc import ABC

class interactive(ABC):    #Subclasses must implement the abstract method interactive (using the @abstractmethod decorator)
    def __init__(self, options ,title="Use arrow keys to select, press Enter to confirm", description="", min_select=0, max_select=None):
        super().__init__()
        self.options = self._check_options(options)
        self.title = title
        self.description = description
        self.current_idx = 0
        self.confirm_keys = ('\r', '\n')
        self.m = False
        self.min_select = min_select
        self.max_select = max_select if max_select is not None else len(options)
        # Initialize selected as empty list
        self.selected = []
        

    def _check_options(self, options):
        if not isinstance(options, list) or len(options) == 0:
            raise ValueError("Options list cannot be empty and must be of type list")
        if not all(isinstance(opt, str) for opt in options):
            raise TypeError("All elements in options list must be of type string")
        return options
# Multi-select functionality added: users can press 'm' to select current option and continue selecting other options until Enter is pressed to complete selection
    def _get_key(self):
        if sys.platform.startswith('win'):
            import msvcrt
            while True:
                if msvcrt.kbhit():
                    key = msvcrt.getch()
                    if key == b'\xe0':
                        key2 = msvcrt.getch()
                        if key2 == b'H':
                            return 'up'
                        elif key2 == b'P':
                            return 'down'
                    else:
                        try:
                            key_char = key.decode('ascii', errors='ignore').lower()
                            if key_char == 'm':
                                return 'm'
                            if key_char == 'r' or key_char == '\r':
                                return '\r'
                            return key_char
                        except:
                            return ''
        else:
            import termios
            import tty
            fd = sys.stdin.fileno()
            old_settings = termios.tcgetattr(fd)
            try:
                tty.setraw(fd)
                key = sys.stdin.read(1)
                if key == '\x1b':
                    sys.stdin.read(1)
                    key2 = sys.stdin.read(1)
                    if key2 == 'A':
                        return 'up'
                    elif key2 == 'B':
                        return 'down'
                return key.lower()
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

    def _clear_screen(self):
        os.system('cls' if sys.platform.startswith('win') else 'clear')

    def _print_menu(self):
        print(self.title)
        if self.description:
            print()
            print(f"Description: {self.description}")
        print()
        # Display selected options (below description)
        if self.selected:
            # Handle nested lists, ensure all elements are strings
            display_items = []
            for item in self.selected:
                if isinstance(item, list):
                    # Flatten nested list
                    for subitem in item:
                        display_items.append(str(subitem))
                else:
                    display_items.append(str(item))
            print("Selected: ", " | ".join(display_items))
        print(f"(Minimum selections: {self.min_select}, Maximum selections: {self.max_select})")
        print()
        for idx, opt in enumerate(self.options):
            if idx == self.current_idx:
                print(f"-> {opt}")
            else:
                print(f"   {opt}")

    def _handle_key(self, key):
        if key == 'up' and self.current_idx > 0:
            self.current_idx -= 1
        elif key == 'down' and self.current_idx < len(self.options) - 1:
            self.current_idx += 1
        elif key == 'm':
            self.m = True
            if not hasattr(self, 'selected'):
                self.selected = []
            current_option = self.options[self.current_idx]
            if current_option not in self.selected:
                self.selected.append(current_option)
            return 'm_select'
        elif key in ['\b', '\x08', 'backspace']:
            if hasattr(self, 'selected') and self.selected and len(self.selected) > 0:
                removed = self.selected.pop()
                print(f"\nRemoved: {removed}")
                return 'delete'
            return None
        elif key in self.confirm_keys:
            if not hasattr(self, 'selected'):
                self.selected = []
            if len(self.selected) >= self.min_select:
                return self.selected
            else:
                print(f"\nAt least {self.min_select} options must be selected, currently selected: {len(self.selected)}")
                return None

        return None

    def run(self):
        # 正常的交互式选择
        # Ensure selected is properly initialized as empty list
        self.selected = []
        self.m = False
        self.max_select = self.max_select if self.max_select is not None else len(self.options)
        
        # First display menu without clearing screen, wait for user to press Enter to start
        self._print_menu()
        print("\nPress Enter to start selection...")
        
        # Wait for user to press Enter
        while True:
            key = self._get_key()
            if key in self.confirm_keys:
                break
        
        # Start normal selection loop
        while True:
            self._clear_screen()
            self._print_menu()
            key = self._get_key()
            result = self._handle_key(key)
            if result == 'delete':
                continue
            if result == 'm_select':
                print(f"\nSelected: {self.selected[-1]}")
                print(f"Currently selected: {self.selected}")
                print("Continue selecting other options...")
                continue
            if isinstance(result, list):
                return result
            # Only process non-enter and non-delete keys, and ensure result is not a list
            if result is not None and not isinstance(result, list) and result not in self.confirm_keys and result != 'delete':
                if len(self.selected) >= self.max_select:
                    print(f"\nMaximum {self.max_select} options can be selected")
                    continue
                if result not in self.selected:
                    self.selected.append(result)
                print(f"\nSelected: {result}")
                print(f"Currently selected: {self.selected}")
                print("Continue selecting other options...")

# Multi-select functionality added here
if __name__ == "__main__":
    test_options = ["Feature Engineering", "Model Training", "Result Visualization", "Exit"]
    selector = interactive(test_options)
    selector.run()
