#!/usr/bin/env python3
"""
Un-Intels - Local AI & Microsoft Office Integration Bridge
Created by Narotech India | https://narotech.in
"""

import sys
import os
import time
import subprocess
import shutil
import re

# ANSI Color Palette for Modern Terminal Styling
class Colors:
    CYAN = '\033[96m'
    MAGENTA = '\033[95m'
    BLUE = '\033[94m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'
    RESET = '\033[0m'
    DIM = '\033[2m'

# Enable Windows ANSI color support if running on NT
if os.name == 'nt':
    os.system('')

def clear_screen():
    """Clears the terminal screen for a clean UI presentation."""
    os.system('cls' if os.name == 'nt' else 'clear')

def bring_window_to_front(title_keyword):
    """Brings matching application window to foreground using Windows Win32 API."""
    if os.name != 'nt':
        return False
    try:
        import ctypes
        user32 = ctypes.windll.user32
        found = [False]
        
        # Exclude web browser windows to prevent browser tabs matching keywords from popping up
        browser_keywords = ['chrome', 'edge', 'firefox', 'brave', 'opera', 'browser', 'google']
        
        def enum_windows_callback(hwnd, extra):
            if user32.IsWindowVisible(hwnd):
                length = user32.GetWindowTextLengthW(hwnd)
                if length > 0:
                    buff = ctypes.create_unicode_buffer(length + 1)
                    user32.GetWindowTextW(hwnd, buff, length + 1)
                    title = buff.value.lower()
                    kw = title_keyword.lower()
                    
                    if kw in title and not any(b in title for b in browser_keywords):
                        user32.ShowWindow(hwnd, 9)  # 9 = SW_RESTORE
                        user32.SetForegroundWindow(hwnd)
                        found[0] = True
            return True
            
        EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.c_int)
        user32.EnumWindows(EnumWindowsProc(enum_windows_callback), 0)
        return found[0]
    except Exception:
        return False

def is_admin():
    """Checks if current Python process has Administrator privileges on Windows."""
    if os.name != 'nt':
        return True
    try:
        import ctypes
        return ctypes.windll.shell32.IsUserAnAdmin() != 0
    except Exception:
        return False

def elevate_to_admin():
    """Triggers UAC dialog and re-opens the script inside an elevated Administrator CMD panel."""
    if is_admin():
        return True
    try:
        import ctypes
        print(f"\n{Colors.YELLOW}[*] Requesting Windows Administrator Elevation (UAC Prompt)...{Colors.RESET}")
        script_path = os.path.abspath(sys.argv[0])
        script_dir = os.path.dirname(script_path)
        python_exe = sys.executable
        cmd_args = f'/k cd /d "{script_dir}" && "{python_exe}" "{script_path}"'
        res = ctypes.windll.shell32.ShellExecuteW(None, "runas", "cmd.exe", cmd_args, script_dir, 1)
        if res > 32:
            print(f"{Colors.GREEN}✓ Launched elevated Administrator CMD panel.{Colors.RESET}")
            sys.exit(0)
        else:
            print(f"{Colors.RED}Elevation was canceled by user.{Colors.RESET}")
            return False
    except Exception as e:
        print(f"{Colors.RED}Failed to elevate privileges: {e}{Colors.RESET}")
        return False

def print_banner():
    """Displays the large stylized ASCII banner, watermark, and branding."""
    clear_screen()
    banner = f"""
{Colors.CYAN}{Colors.BOLD}
  ██╗   ██╗███╗   ██╗    ██╗███╗   ██╗████████╗███████╗██╗     ███████╗
  ██║   ██║████╗  ██║    ██║████╗  ██║╚══██╔══╝██╔════╝██║     ██╔════╝
  ██║   ██║██╔██╗ ██║    ██║██╔██╗ ██║   ██║   █████╗  ██║     ███████╗
  ██║   ██║██║╚██╗██║    ██║██║╚██╗██║   ██║   ██╔══╝  ██║     ╚════██║
  ╚██████╔╝██║ ╚████║    ██║██║ ╚████║   ██║   ███████╗███████╗███████║
   ╚═════╝ ╚═╝  ╚═══╝    ╚═╝╚═╝  ╚═══╝   ╚═╝   ╚══════╝╚══════╝╚══════╝
{Colors.RESET}"""
    print(banner)
    print(f"{Colors.YELLOW}{Colors.BOLD}  Un-Intels By Narotech India{Colors.RESET}")
    print(f"{Colors.DIM}──────────────────────────────────────────────────────────────────────────{Colors.RESET}\n")

def check_privacy_consent():
    """Displays Privacy Policy and requires explicit user consent."""
    print(f"{Colors.BOLD}Privacy Policy Notice:{Colors.RESET}")
    print(f"{Colors.CYAN}By using Un-Intels, you agree that your chat queries interact with your")
    print(f"local AI server and Microsoft Office. No user data is transmitted externally")
    print(f"or stored by Narotech India.{Colors.RESET}\n")
    
    consent = input(f"{Colors.BOLD}Do you accept the Privacy Policy? (Y/N): {Colors.RESET}").strip()
    if consent.lower() not in ['y', 'yes']:
        print(f"\n{Colors.RED}Consent denied. Exiting Un-Intels.{Colors.RESET}")
        sys.exit(0)
    print(f"\n{Colors.GREEN}✓ Consent accepted.{Colors.RESET}\n")
    time.sleep(0.5)

AUTO_EXECUTE = False

def print_control_header(office_mgr, ai_client):
    """Prints a stylized header box displaying shortcut keys, active app, and model status."""
    conn, models = ai_client.check_connection()
    model_name = models[0] if (conn and isinstance(models, list) and models) else ("Connected" if conn else "OFFLINE")
    if len(model_name) > 32:
        model_name = model_name[:29] + "..."
    
    app_str = f"Microsoft {office_mgr.active_app_type}"
    auto_str = "ENABLED (Direct)" if AUTO_EXECUTE else "DISABLED (Confirm)"
    auto_color = Colors.GREEN if AUTO_EXECUTE else Colors.YELLOW

    header = f"""{Colors.CYAN}{Colors.BOLD}┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                 UN-INTELS COMMAND & CONTROL PANEL                                │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {Colors.YELLOW}TARGET APP{Colors.CYAN} : {app_str:<18}  │  {Colors.YELLOW}ACTIVE MODEL{Colors.CYAN}: {model_name:<38} │
│ {Colors.YELLOW}AUTO-EXECUTE{Colors.CYAN}: {auto_color}{auto_str:<18}{Colors.CYAN} │                                                      │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ {Colors.BOLD}SHORTCUT KEYS & COMMAND DIRECTORY:{Colors.RESET}{Colors.CYAN}                                                               │
│  • {Colors.GREEN}/a{Colors.CYAN}  or {Colors.GREEN}/auto{Colors.CYAN}    : Toggle Direct Auto-Execution     • {Colors.GREEN}/s{Colors.CYAN}  or {Colors.GREEN}/switch{Colors.CYAN} : Switch Target Office App║
│  • {Colors.GREEN}/m{Colors.CYAN}  or {Colors.GREEN}/models{Colors.CYAN}  : Select & Load LM Studio Model   • {Colors.GREEN}/o{Colors.CYAN}  or {Colors.GREEN}/open{Colors.CYAN}    : Open/Focus Desktop Apps  ║
│  • {Colors.GREEN}/st{Colors.CYAN} or {Colors.GREEN}/status{Colors.CYAN}  : System Dashboard Status          • {Colors.GREEN}/r{Colors.CYAN}  or {Colors.GREEN}/reset{Colors.CYAN}   : Reset Chat Context       ║
│  • {Colors.GREEN}/u{Colors.CYAN}  or {Colors.GREEN}/url{Colors.CYAN}     : Change Local AI Endpoint URL     • {Colors.GREEN}/c{Colors.CYAN}  or {Colors.GREEN}/cls{Colors.CYAN}    : Clear Screen             ║
│  • {Colors.GREEN}/q{Colors.CYAN}  or {Colors.GREEN}/exit{Colors.CYAN}    : Quit & Exit Un-Intels                                                ║
└──────────────────────────────────────────────────────────────────────────────────────────────────┘{Colors.RESET}"""
    print(header)

# Guarded module imports for non-auto-install compliance
MISSING_DEPS = []

try:
    import psutil
except ImportError:
    psutil = None
    MISSING_DEPS.append("psutil")

try:
    import requests
except ImportError:
    requests = None
    MISSING_DEPS.append("requests")

try:
    import win32com.client
    import win32com.client.dynamic
    import pythoncom
except ImportError:
    win32com = None
    pythoncom = None
    MISSING_DEPS.append("pywin32")

try:
    import xlwings
except ImportError:
    xlwings = None
    MISSING_DEPS.append("xlwings")

def install_packages():
    """Attempts multiple installation strategies to guarantee packages are installed."""
    packages = ["xlwings", "requests", "pywin32", "psutil"]
    commands = [
        [sys.executable, "-m", "pip", "install"] + packages,
        [sys.executable, "-m", "pip", "install", "--user"] + packages,
        ["pip", "install"] + packages,
        ["py", "-m", "pip", "install"] + packages,
    ]
    
    success = False
    last_error = None
    for cmd in commands:
        try:
            print(f"{Colors.DIM}Executing: {' '.join(cmd)}{Colors.RESET}")
            res = subprocess.run(cmd, check=True)
            if res.returncode == 0:
                success = True
                break
        except Exception as err:
            last_error = err
            continue

    if not success:
        raise RuntimeError(f"All pip install attempts failed. Last error: {last_error}")

    # Register pywin32 system DLLs if pywin32 was installed
    try:
        subprocess.run([sys.executable, "-m", "pywin32_postinstall", "-install"], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    except Exception:
        pass

    return True

def verify_and_fix_dependencies():
    """Performs dependency check and offers interactive troubleshooting loop."""
    global psutil, requests, win32com, pythoncom, xlwings, MISSING_DEPS
    if not MISSING_DEPS:
        print(f"{Colors.GREEN}✓ All required components are verified and ready.{Colors.RESET}")
        return True

    print(f"{Colors.RED}{Colors.BOLD}[!] Missing Required Dependencies Detected:{Colors.RESET}")
    for dep in MISSING_DEPS:
        print(f"   • {dep}")
    print()

    ans = input(f"{Colors.BOLD}Would you like to proceed to troubleshoot and fix the missing dependencies? (Y/N): {Colors.RESET}").strip()
    if ans.lower() not in ['y', 'yes']:
        print(f"\n{Colors.YELLOW}Cannot continue without required dependencies. Exiting.{Colors.RESET}")
        sys.exit(1)

    print(f"\n{Colors.CYAN}{Colors.BOLD}Troubleshooting & Fix Instructions:{Colors.RESET}")
    print("To install the missing Python libraries manually, execute the following command:")
    print(f"  {Colors.GREEN}pip install xlwings requests pywin32 psutil{Colors.RESET}\n")

    auto_fix = input(f"{Colors.BOLD}Would you like Un-Intels to run package installation for you now? (Y/N): {Colors.RESET}").strip()
    if auto_fix.lower() in ['y', 'yes']:
        print(f"\n{Colors.YELLOW}Running robust package installation...{Colors.RESET}\n")
        try:
            install_packages()
            print(f"\n{Colors.GREEN}✓ Installation completed successfully! Restarting Un-Intels...{Colors.RESET}\n")
            time.sleep(1)
            try:
                os.execv(sys.executable, [sys.executable] + sys.argv)
            except Exception:
                subprocess.run([sys.executable] + sys.argv)
                sys.exit(0)
        except Exception as e:
            print(f"\n{Colors.RED}Automatic installation failed: {e}{Colors.RESET}")
            print(f"Please manually run: {Colors.GREEN}pip install xlwings requests pywin32 psutil{Colors.RESET}")
            sys.exit(1)
    else:
        print(f"\n{Colors.YELLOW}Please install the missing libraries and restart Un-Intels.{Colors.RESET}")
        sys.exit(1)

# Office Application Manager (Process Scanning & Hooking)
class OfficeManager:
    APP_EXES = {
        'excel.exe': ('Excel', 'Excel.Application'),
        'winword.exe': ('Word', 'Word.Application'),
        'powerpnt.exe': ('PowerPoint', 'PowerPoint.Application')
    }

    def __init__(self):
        self.active_app_type = None  # 'Excel', 'Word', or 'PowerPoint'
        self.com_app = None

    def bring_to_front(self):
        """Forces the current target Office application window to the foreground."""
        if not self.active_app_type or not self.com_app:
            return
        try:
            self.com_app.Visible = True
            self.com_app.UserControl = True
            try:
                self.com_app.Activate()
            except Exception:
                pass
            bring_window_to_front(self.active_app_type)
        except Exception:
            pass

    def scan_running_apps(self):
        """Scans running system processes for MS Office executables."""
        found_apps = {}
        if not psutil:
            return found_apps

        for proc in psutil.process_iter(['name']):
            try:
                name = proc.info['name'].lower() if proc.info['name'] else ''
                if name in self.APP_EXES:
                    app_name, prog_id = self.APP_EXES[name]
                    found_apps[app_name] = prog_id
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
        return found_apps

    def launch_app(self, app_name):
        """Launches a new instance of the specified MS Office application and brings window to front."""
        print(f"\n{Colors.CYAN}Launching new instance of Microsoft {app_name}...{Colors.RESET}")
        prog_id_map = {'Excel': 'Excel.Application', 'Word': 'Word.Application', 'PowerPoint': 'PowerPoint.Application'}
        prog_id = prog_id_map.get(app_name)
        if not prog_id:
            raise ValueError(f"Unknown application name: {app_name}")

        try:
            if not win32com:
                raise ImportError("win32com library is not available.")
            
            com_app = win32com.client.dynamic.Dispatch(prog_id)
            com_app.Visible = True
            com_app.UserControl = True
            self.com_app = com_app
            self.active_app_type = app_name

            # Ensure an active document/workbook/presentation exists
            if app_name == 'Excel':
                if com_app.Workbooks.Count == 0:
                    com_app.Workbooks.Add()
            elif app_name == 'Word':
                if com_app.Documents.Count == 0:
                    com_app.Documents.Add()
            elif app_name == 'PowerPoint':
                if com_app.Presentations.Count == 0:
                    com_app.Presentations.Add()

            try:
                com_app.Activate()
            except Exception:
                pass
            bring_window_to_front(app_name)

            print(f"{Colors.GREEN}✓ Successfully launched & opened Microsoft {app_name}!{Colors.RESET}")
            return True
        except Exception as e:
            print(f"{Colors.RED}Failed to launch {app_name}: {e}{Colors.RESET}")
            return False

    def hook_existing_app(self, app_name):
        """Hooks into an already open instance of MS Office and brings window to front."""
        print(f"\n{Colors.CYAN}Connecting to active Microsoft {app_name} instance...{Colors.RESET}")
        prog_id_map = {'Excel': 'Excel.Application', 'Word': 'Word.Application', 'PowerPoint': 'PowerPoint.Application'}
        prog_id = prog_id_map[app_name]
        try:
            if not win32com:
                raise ImportError("win32com library is not available.")
            
            raw_app = win32com.client.GetActiveObject(prog_id)
            com_app = win32com.client.dynamic.Dispatch(raw_app)
            com_app.Visible = True
            com_app.UserControl = True
            self.com_app = com_app
            self.active_app_type = app_name

            # Ensure active container exists
            if app_name == 'Excel':
                if com_app.Workbooks.Count == 0:
                    com_app.Workbooks.Add()
            elif app_name == 'Word':
                if com_app.Documents.Count == 0:
                    com_app.Documents.Add()
            elif app_name == 'PowerPoint':
                if com_app.Presentations.Count == 0:
                    com_app.Presentations.Add()

            try:
                com_app.Activate()
            except Exception:
                pass
            bring_window_to_front(app_name)

            print(f"{Colors.GREEN}✓ Successfully hooked into active {app_name}!{Colors.RESET}")
            return True
        except Exception as e:
            print(f"{Colors.YELLOW}Could not attach to active object ({e}). Launching new instance...{Colors.RESET}")
            return self.launch_app(app_name)

    def select_and_connect(self):
        """Intelligently detects open apps and prompts user based on system state."""
        print(f"{Colors.BOLD}\n[Scanning for running Microsoft Office applications...]{Colors.RESET}")
        found_apps = self.scan_running_apps()

        if len(found_apps) == 0:
            print(f"{Colors.YELLOW}No open MS Office applications were detected.{Colors.RESET}")
            print(f"Which application would you like to open?\n  1. Excel\n  2. Word\n  3. PowerPoint")
            choice = input(f"{Colors.BOLD}Enter choice (1-3): {Colors.RESET}").strip()
            app_map = {'1': 'Excel', '2': 'Word', '3': 'PowerPoint'}
            selected = app_map.get(choice, 'Excel')
            self.launch_app(selected)

        elif len(found_apps) == 1:
            app_name = list(found_apps.keys())[0]
            ans = input(f"{Colors.BOLD}{app_name} is Open, Do you want to continue with {app_name}? (Y/N): {Colors.RESET}").strip()
            if ans.lower() in ['y', 'yes']:
                self.hook_existing_app(app_name)
            else:
                print(f"\nWhich other application would you like to open?")
                print("  1. Excel\n  2. Word\n  3. PowerPoint")
                choice = input(f"{Colors.BOLD}Enter choice (1-3): {Colors.RESET}").strip()
                app_map = {'1': 'Excel', '2': 'Word', '3': 'PowerPoint'}
                selected = app_map.get(choice, 'Excel')
                self.launch_app(selected)

        else:
            print(f"{Colors.CYAN}{Colors.BOLD}Multiple MS Office Applications Detected:{Colors.RESET}")
            app_list = list(found_apps.keys())
            for idx, name in enumerate(app_list, 1):
                print(f"  {idx}. {name}")
            
            choice = input(f"{Colors.BOLD}Type the name or number of the app you want to interact with: {Colors.RESET}").strip()
            selected = None
            if choice.isdigit() and 1 <= int(choice) <= len(app_list):
                selected = app_list[int(choice) - 1]
            else:
                for name in app_list:
                    if name.lower() == choice.lower():
                        selected = name
                        break
            if not selected:
                selected = app_list[0]
                print(f"{Colors.YELLOW}Defaulting selection to {selected}{Colors.RESET}")
            
            self.hook_existing_app(selected)

# Local AI Client (LM Studio / OpenAI Compatible Endpoint)
class LocalAIClient:
    def __init__(self, base_url="http://localhost:1234/v1"):
        self.base_url = base_url.rstrip('/')

    def get_lms_cli_path(self):
        """Finds lms CLI executable path if installed."""
        user_lms_bin = os.path.expanduser(r"~\.lmstudio\bin\lms.exe")
        if os.path.exists(user_lms_bin):
            return user_lms_bin
        return shutil.which("lms")

    def check_connection(self):
        """Verifies connection to local AI endpoint."""
        if not requests:
            return False, "requests module missing"
        try:
            resp = requests.get(f"{self.base_url}/models", timeout=3)
            if resp.status_code == 200:
                data = resp.json()
                models = [m.get('id') for m in data.get('data', [])]
                return True, models
        except Exception as e:
            return False, str(e)
        return False, "Non-200 response"

    def get_disk_models(self):
        """Discovers downloaded models on disk using LM Studio CLI."""
        lms_cli = self.get_lms_cli_path()
        if not lms_cli:
            return []
        try:
            res = subprocess.run([lms_cli, "ls"], capture_output=True, text=True, timeout=10)
            lines = res.stdout.split('\n')
            models = []
            section = None
            for line in lines:
                l = line.strip()
                if not l:
                    continue
                if l.startswith("LLM"):
                    section = "LLM"
                    continue
                elif l.startswith("EMBEDDING"):
                    section = "EMBEDDING"
                    continue
                
                parts = line.split()
                if parts and not l.startswith("You have") and not l.startswith("PARAMS"):
                    model_key = parts[0]
                    if model_key not in ["LLM", "EMBEDDING", "PARAMS", "ARCH", "SIZE", "DEVICE"]:
                        models.append({
                            "key": model_key,
                            "type": section or "LLM"
                        })
            return models
        except Exception:
            return []

    def load_model(self, model_key):
        """Loads a model into LM Studio RAM/VRAM using lms load."""
        lms_cli = self.get_lms_cli_path()
        if not lms_cli:
            print(f"{Colors.RED}LM Studio CLI (lms) not found to load model.{Colors.RESET}")
            return False
        print(f"\n{Colors.YELLOW}Loading model '{model_key}' into LM Studio memory...{Colors.RESET}")
        try:
            res = subprocess.run([lms_cli, "load", model_key, "-y", "--gpu", "max"], capture_output=True, text=True, timeout=120)
            if res.returncode == 0:
                print(f"{Colors.GREEN}✓ Successfully loaded '{model_key}' into LM Studio!{Colors.RESET}\n")
                return True
            else:
                print(f"{Colors.RED}Failed to load model: {res.stderr or res.stdout}{Colors.RESET}\n")
                return False
        except Exception as e:
            print(f"{Colors.RED}Error loading model: {e}{Colors.RESET}\n")
            return False

    def select_and_load_model(self, force_interactive=False):
        """Scans disk models and ensures a valid LLM Chat model is loaded into LM Studio."""
        disk_models = self.get_disk_models()
        conn, loaded_models = self.check_connection()
        
        has_chat_model = False
        if conn and isinstance(loaded_models, list):
            for m in loaded_models:
                if 'embed' not in m.lower() and 'nomic' not in m.lower() and 'bge' not in m.lower():
                    has_chat_model = True
                    break

        if not force_interactive and has_chat_model:
            return True

        if disk_models:
            print(f"\n{Colors.CYAN}{Colors.BOLD}┌── LM Studio Model Manager ─────────────────────────────────────────────┐{Colors.RESET}")
            llm_models = [m for m in disk_models if m.get('type') == 'LLM']
            embed_models = [m for m in disk_models if m.get('type') == 'EMBEDDING']
            
            all_selectable = llm_models + embed_models
            if not all_selectable:
                print(f"{Colors.YELLOW}│ No local models detected on disk via 'lms ls'.                         │{Colors.RESET}")
                print(f"{Colors.CYAN}{Colors.BOLD}└────────────────────────────────────────────────────────────────────────┘{Colors.RESET}\n")
                return False

            for idx, m in enumerate(all_selectable, 1):
                tag = "[LLM Chat Model]" if m['type'] == 'LLM' else "[Embedding Model]"
                color = Colors.GREEN if m['type'] == 'LLM' else Colors.DIM
                print(f"{Colors.CYAN}│{Colors.RESET} {idx}. {color}{m['key']}{Colors.RESET} {Colors.YELLOW}{tag}{Colors.RESET}")
            print(f"{Colors.CYAN}{Colors.BOLD}└────────────────────────────────────────────────────────────────────────┘{Colors.RESET}\n")

            if not has_chat_model and llm_models and not force_interactive:
                target_model = llm_models[0]['key']
                print(f"{Colors.YELLOW}[!] Currently loaded model is an embedding model or inactive.{Colors.RESET}")
                print(f"{Colors.GREEN}    Auto-selecting LLM Chat Model '{target_model}' for code generation...{Colors.RESET}")
                return self.load_model(target_model)

            choice = input(f"{Colors.BOLD}Select a model number to load into LM Studio (or press Enter to skip): {Colors.RESET}").strip()
            if choice.isdigit() and 1 <= int(choice) <= len(all_selectable):
                selected_key = all_selectable[int(choice) - 1]['key']
                return self.load_model(selected_key)
        return True

    def launch_and_connect_lm_studio(self, timeout_sec=30):
        """Launches LM Studio application and initializes local AI server on port 1234."""
        print(f"\n{Colors.CYAN}{Colors.BOLD}[+] Launching LM Studio & starting local AI server...{Colors.RESET}")
        
        # 1. Launch LM Studio GUI Application Window
        gui_paths = [
            r"C:\Program Files\LM Studio\LM Studio.exe",
            os.path.expandvars(r"%LOCALAPPDATA%\Programs\LM-Studio\LM Studio.exe"),
            os.path.expandvars(r"%LOCALAPPDATA%\Programs\LM Studio\LM Studio.exe"),
            os.path.expandvars(r"%PROGRAMFILES%\LM Studio\LM Studio.exe"),
        ]
        gui_opened = False
        for gpath in gui_paths:
            if os.path.exists(gpath):
                try:
                    subprocess.Popen([gpath], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                    print(f"{Colors.GREEN}  • Opened LM Studio Application ({os.path.basename(gpath)}){Colors.RESET}")
                    gui_opened = True
                    break
                except Exception:
                    pass

        # 2. Trigger local server start via CLI
        lms_cli = self.get_lms_cli_path()
        if lms_cli:
            try:
                subprocess.Popen([lms_cli, "server", "start"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                print(f"{Colors.GREEN}  • Started local AI server on port 1234 via CLI{Colors.RESET}")
            except Exception:
                pass

        # 3. Bring LM Studio window to front
        if gui_opened:
            bring_window_to_front("LM Studio")

        # 4. Poll connection status
        start_time = time.time()
        print(f"{Colors.YELLOW}Waiting for Local AI Server to initialize on {self.base_url}...{Colors.RESET}", end="", flush=True)
        
        connected = False
        info = None
        while time.time() - start_time < timeout_sec:
            conn, info = self.check_connection()
            if conn:
                connected = True
                print(f"\n{Colors.GREEN}✓ Connection successfully established!{Colors.RESET}")
                break
            print(".", end="", flush=True)
            time.sleep(1)
            
        if not connected:
            print()
            return False, "Server did not respond within timeout period."

        # 5. Auto-load LLM model if only embedding model is loaded
        self.select_and_load_model(force_interactive=False)
        return self.check_connection()

    def chat_completion(self, messages):
        """Sends chat history to local AI API and returns assistant response."""
        url = f"{self.base_url}/chat/completions"
        
        # Discover active/loaded model key to explicitly populate OpenAI model field
        model_key = None
        conn, models = self.check_connection()
        if conn and isinstance(models, list) and models:
            llm_models = [m for m in models if "embed" not in m.lower() and "nomic" not in m.lower()]
            if llm_models:
                model_key = llm_models[0]
            else:
                model_key = models[0]

        payload = {
            "messages": messages,
            "temperature": 0.2,
            "max_tokens": 1024
        }
        if model_key:
            payload["model"] = model_key

        headers = {"Content-Type": "application/json"}
        response = requests.post(url, headers=headers, json=payload, timeout=60)
        
        if response.status_code != 200:
            try:
                err_data = response.json()
                err_msg = err_data.get('error', {}).get('message', response.text)
            except Exception:
                err_msg = response.text
            raise RuntimeError(f"HTTP {response.status_code} - {err_msg}")

        res_data = response.json()
        return res_data['choices'][0]['message']['content']

def generate_system_prompt(app_type):
    """Generates context-aware system prompt for dual conversational/office mode."""
    return f"""You are Un-Intels, an intelligent AI bridge connecting local AI models with Microsoft {app_type} on Windows.

OPERATIONAL MODES:
1. CONVERSATIONAL MODE:
If the user asks general questions, requests advice, or has casual conversation:
Provide a clear, helpful plain text answer.

2. OFFICE ACTION MODE:
If the user asks to perform an action, modify data, format text, or create content inside Microsoft {app_type}:
You MUST provide a clear explanation followed by executable Python code inside a ```python ... ``` code block.

CRITICAL COM CODE RULES FOR {app_type}:
- Do NOT call `win32.GetActiveObject` or re-initialize `win32com`! Use the pre-bound variables: `app`, `wb`, `sheet` (Excel), `doc` (Word), or `prs` (PowerPoint).
- In Excel COM, DO NOT call `sheet.Columns(number)` or `sheet.Rows(number)` as functions. Use `sheet.Columns.Item(number)`, `sheet.Cells(row, col)`, or `sheet.Range("A:D")`.
- Format numbers using `sheet.Range("B2:D10").NumberFormat = "$#,##0.00"` or `sheet.Columns.Item(col).NumberFormat = "#,##0.00"`.

RUNTIME ENVIRONMENT CONTEXT FOR {app_type}:
- 'win32com': win32com.client module
- 'xlwings': xlwings module (for Excel)
- 'app': Active COM Application object for Microsoft {app_type}
- Excel context: 'wb' (active workbook), 'sheet' (active worksheet), 'xlw_app' (xlwings app), 'xlw_book' (xlwings book)
- Word context: 'doc' (active document)
- PowerPoint context: 'prs' (active presentation)

EXAMPLES FOR EXCEL ({app_type}):
User request: "Create salary sheet"
Response:
I will populate a Salary Sheet with headers, sample employee data, formulas, and formatting.
```python
# Excel Action
headers = ["Name", "Hours", "Rate", "Salary"]
for col, header in enumerate(headers, start=1):
    sheet.Cells(1, col).Value = header

data = [("Alice", 160, 25.0), ("Bob", 150, 30.5), ("Carol", 170, 28.75)]
for i, (name, hours, rate) in enumerate(data, start=2):
    sheet.Cells(i, 1).Value = name
    sheet.Cells(i, 2).Value = hours
    sheet.Cells(i, 3).Value = rate
    sheet.Cells(i, 4).Formula = f"=B{{i}}*C{{i}}"

total_row = len(data) + 2
sheet.Cells(total_row, 1).Value = "Total"
sheet.Cells(total_row, 4).Formula = f"=SUM(D2:D{{total_row-1}})"
sheet.Range(f"C2:D{{total_row}}").NumberFormat = "$#,##0.00"
sheet.Columns.AutoFit()
```
Ensure code blocks contain clean, self-contained Python code using pre-bound runtime context.
"""

def sanitize_office_code(code_str, app_type):
    """Sanitizes common PyWin32/COM code issues generated by LLMs."""
    lines = code_str.split('\n')
    sanitized = []
    for line in lines:
        # Remove GetActiveObject calls that overwrite pre-bound app context
        if 'GetActiveObject' in line and ('Excel' in line or 'Word' in line or 'PowerPoint' in line):
            continue
        if 'import win32com.client as win32' in line or 'import win32com.client' in line:
            continue
        # Convert sheet.Columns(int) to sheet.Columns.Item(int) to fix PyWin32 gen_py __len__ bug
        line = re.sub(r'sheet\.Columns\((\d+)\)', r'sheet.Columns.Item(\1)', line)
        line = re.sub(r'sheet\.Rows\((\d+)\)', r'sheet.Rows.Item(\1)', line)
        sanitized.append(line)
    return '\n'.join(sanitized)

def execute_generated_code(code_str, office_mgr, ai_client=None, chat_history=None):
    """Executes AI-generated Python code safely with automatic COM sanitization and AI self-healing."""
    app_type = office_mgr.active_app_type
    com_app = office_mgr.com_app

    # Force Office window to front before execution
    office_mgr.bring_to_front()

    exec_globals = {
        'win32com': win32com.client.dynamic if win32com else None,
        'xlwings': xlwings,
        'app': com_app,
    }

    if app_type == 'Excel':
        wb = getattr(com_app, 'ActiveWorkbook', None) if com_app else None
        sheet = getattr(wb, 'ActiveSheet', None) if wb else None
        xlw_app = None
        xlw_book = None
        if xlwings:
            try:
                xlw_app = xlwings.apps.active
                xlw_book = xlwings.books.active
            except Exception:
                pass
        exec_globals.update({
            'wb': wb,
            'sheet': sheet,
            'xlw_app': xlw_app,
            'xlw_book': xlw_book
        })
    elif app_type == 'Word':
        doc = getattr(com_app, 'ActiveDocument', None) if com_app else None
        exec_globals.update({'doc': doc})
    elif app_type == 'PowerPoint':
        prs = getattr(com_app, 'ActivePresentation', None) if com_app else None
        exec_globals.update({'prs': prs})

    print(f"\n{Colors.CYAN}Executing action on Microsoft {app_type}...{Colors.RESET}")

    # Step 1: Try running original code
    try:
        exec(code_str, exec_globals)
        print(f"{Colors.GREEN}{Colors.BOLD}✓ Action executed successfully on {app_type}!{Colors.RESET}\n")
        return True
    except Exception as err1:
        # Step 2: Try running sanitized code
        clean_code = sanitize_office_code(code_str, app_type)
        try:
            exec(clean_code, exec_globals)
            print(f"{Colors.GREEN}{Colors.BOLD}✓ Action executed successfully on {app_type} (via COM sanitization)!{Colors.RESET}\n")
            return True
        except Exception as err2:
            print(f"\n{Colors.RED}{Colors.BOLD}[!] Execution Error Occurred:{Colors.RESET}")
            print(f"{Colors.RED}{err2}{Colors.RESET}")
            
            # Step 3: Trigger AI Self-Healing Code Repair
            if ai_client and chat_history:
                print(f"{Colors.YELLOW}[*] Requesting Local AI code auto-fix...{Colors.RESET}")
                fix_prompt = f"The generated Python code for Microsoft {app_type} failed with runtime error: '{err2}'. Fix the code. Use pre-bound variables 'app', 'wb', 'sheet' directly without GetActiveObject or calling sheet.Columns(int). Output ONLY the corrected python block."
                try:
                    chat_history.append({"role": "user", "content": fix_prompt})
                    fixed_response = ai_client.chat_completion(chat_history)
                    chat_history.append({"role": "assistant", "content": fixed_response})
                    fixed_code = extract_python_code(fixed_response)
                    if fixed_code:
                        print(f"{Colors.CYAN}{Colors.BOLD}AI Assistant (Self-Healed Code):{Colors.RESET}\n")
                        for line in fixed_code.split('\n'):
                            print(f"{Colors.YELLOW}│ {Colors.RESET}{line}")
                        print()
                        exec(sanitize_office_code(fixed_code, app_type), exec_globals)
                        print(f"{Colors.GREEN}{Colors.BOLD}✓ Self-healed action executed successfully on {app_type}!{Colors.RESET}\n")
                        return True
                except Exception as err3:
                    print(f"{Colors.RED}Auto-fix execution failed: {err3}{Colors.RESET}\n")

            print(f"{Colors.YELLOW}Troubleshooting Tip: Ensure Microsoft {app_type} is active, visible, and not editing a cell or modal dialog.{Colors.RESET}\n")
            return False

def extract_python_code(text):
    """Extracts Python code block from markdown response."""
    if "```python" in text:
        parts = text.split("```python")
        code = parts[1].split("```")[0]
        return code.strip()
    elif "```" in text:
        parts = text.split("```")
        code = parts[1].split("```")[0]
        return code.strip()
    return None

def main():
    """Main application loop."""
    global AUTO_EXECUTE
    print_banner()
    if os.name == 'nt' and not is_admin():
        print(f"{Colors.YELLOW}[!] Notice: Un-Intels is currently running without Administrator privileges.{Colors.RESET}")
        ans = input(f"{Colors.BOLD}Would you like to elevate and relaunch in an Administrator CMD panel? (Y/N): {Colors.RESET}").strip()
        if ans.lower() in ['y', 'yes']:
            elevate_to_admin()

    check_privacy_consent()
    verify_and_fix_dependencies()

    office_mgr = OfficeManager()
    office_mgr.select_and_connect()

    ai_client = LocalAIClient()
    connected, info = ai_client.check_connection()

    if connected:
        ai_client.select_and_load_model(force_interactive=False)
        connected, info = ai_client.check_connection()
        model_str = f"Available Models: {', '.join(info)}" if isinstance(info, list) else "Connected"
        print(f"{Colors.GREEN}✓ Connected to Local AI Server ({ai_client.base_url}). {model_str}{Colors.RESET}\n")
    else:
        print(f"{Colors.YELLOW}[!] Local AI Server Warning: Could not reach {ai_client.base_url} ({info}){Colors.RESET}")
        launch_ans = input(f"{Colors.BOLD}Would you like Un-Intels to launch LM Studio and start the local AI server now? (Y/N): {Colors.RESET}").strip()
        if launch_ans.lower() in ['y', 'yes']:
            connected, info = ai_client.launch_and_connect_lm_studio()
            if connected:
                model_str = f"Available Models: {', '.join(info)}" if isinstance(info, list) else "Connected"
                print(f"\n{Colors.GREEN}✓ Connected to Local AI Server! {model_str}{Colors.RESET}\n")
            else:
                print(f"\n{Colors.RED}[!] Could not establish connection: {info}{Colors.RESET}")
                print(f"{Colors.YELLOW}    Ensure LM Studio is open and local server is enabled in settings (port 1234).{Colors.RESET}\n")
        else:
            print(f"{Colors.YELLOW}    You can change the AI server endpoint anytime using command '/url <URL>'{Colors.RESET}\n")

    print_control_header(office_mgr, ai_client)

    chat_history = [
        {"role": "system", "content": generate_system_prompt(office_mgr.active_app_type)}
    ]

    while True:
        try:
            prompt_label = f"{Colors.BOLD}{Colors.CYAN}Un-Intels [{office_mgr.active_app_type}]{Colors.RESET} > "
            user_input = input(prompt_label).strip()

            if not user_input:
                continue

            if user_input.lower() in ['/q', '/exit', 'exit', 'quit', '/quit']:
                print(f"\n{Colors.YELLOW}Thank you for using Un-Intels By Narotech India. Goodbye!{Colors.RESET}")
                break

            elif user_input.lower() in ['/s', '/switch', '/apps', '/app']:
                office_mgr.select_and_connect()
                chat_history[0] = {"role": "system", "content": generate_system_prompt(office_mgr.active_app_type)}
                print(f"{Colors.GREEN}✓ Switched active Office application context to {office_mgr.active_app_type}{Colors.RESET}\n")
                print_control_header(office_mgr, ai_client)
                continue

            elif user_input.lower() in ['/m', '/models', '/model']:
                ai_client.select_and_load_model(force_interactive=True)
                print_control_header(office_mgr, ai_client)
                continue

            elif user_input.lower() in ['/o', '/open', '/focus']:
                print(f"\n{Colors.CYAN}Bringing LM Studio and Microsoft {office_mgr.active_app_type} to front...{Colors.RESET}")
                bring_window_to_front("LM Studio")
                office_mgr.bring_to_front()
                print(f"{Colors.GREEN}✓ Windows activated.{Colors.RESET}\n")
                continue

            elif user_input.lower() in ['/st', '/status']:
                print(f"\n{Colors.CYAN}{Colors.BOLD}Un-Intels System Status Dashboard:{Colors.RESET}")
                print(f" • Active Office Application : Microsoft {office_mgr.active_app_type}")
                print(f" • Local AI Server URL       : {ai_client.base_url}")
                conn, models = ai_client.check_connection()
                print(f" • Server Connection Status  : {'CONNECTED' if conn else 'DISCONNECTED'}")
                print(f" • Active/Loaded Models      : {', '.join(models) if isinstance(models, list) else models}\n")
                continue

            elif user_input.lower() in ['/r', '/reset', '/clear_chat']:
                chat_history = [{"role": "system", "content": generate_system_prompt(office_mgr.active_app_type)}]
                print(f"{Colors.GREEN}✓ Chat conversation history reset.{Colors.RESET}\n")
                continue

            elif user_input.lower() in ['/c', '/cls', '/clear']:
                print_banner()
                print_control_header(office_mgr, ai_client)
                continue

            elif user_input.lower().startswith('/u') or user_input.lower().startswith('/url'):
                parts = user_input.split(maxsplit=1)
                if len(parts) > 1:
                    ai_client.base_url = parts[1].strip()
                else:
                    new_url = input("Enter Local AI Base URL (e.g. http://localhost:1234/v1): ").strip()
                    if new_url:
                        ai_client.base_url = new_url
                conn, msg = ai_client.check_connection()
                if conn:
                    print(f"{Colors.GREEN}✓ Connected to {ai_client.base_url}{Colors.RESET}\n")
                    print_control_header(office_mgr, ai_client)
                else:
                    print(f"{Colors.RED}Failed to connect to {ai_client.base_url}: {msg}{Colors.RESET}\n")
                continue

            elif user_input.lower() in ['/a', '/auto']:
                AUTO_EXECUTE = not AUTO_EXECUTE
                state_str = "ENABLED (Actions run directly on Office)" if AUTO_EXECUTE else "DISABLED (Confirmation prompt active)"
                print(f"{Colors.GREEN}✓ Auto-Execution Mode: {state_str}{Colors.RESET}\n")
                print_control_header(office_mgr, ai_client)
                continue

            elif user_input.lower() in ['/h', '/help']:
                print(f"\n{Colors.CYAN}{Colors.BOLD}Un-Intels Help & Shortcut Guide:{Colors.RESET}")
                print(f" • General queries are answered naturally in plain text chat mode.")
                print(f" • Office requests (e.g. 'Summarise Column 4' or 'Bold title') generate Python action code.")
                print(f" • Shortcut Keys & Commands:")
                print(f"   - /a  or /auto   : Toggle direct auto-execution mode (skip Y/N)")
                print(f"   - /m  or /models : Select & load downloaded LLM model into LM Studio")
                print(f"   - /s  or /switch : Switch active target Office app (Excel/Word/PowerPoint)")
                print(f"   - /o  or /open   : Restore & focus LM Studio GUI & Office windows to front")
                print(f"   - /st or /status : Display system status dashboard")
                print(f"   - /r  or /reset  : Reset current conversation context")
                print(f"   - /u  or /url    : Change Local AI endpoint URL")
                print(f"   - /c  or /cls    : Clear terminal screen")
                print(f"   - /q  or /exit   : Exit Un-Intels\n")
                continue

            chat_history.append({"role": "user", "content": user_input})

            print(f"{Colors.DIM}[Processing request via Local AI...]{Colors.RESET}")
            try:
                ai_response = ai_client.chat_completion(chat_history)
            except Exception as e:
                print(f"\n{Colors.RED}Error communicating with Local AI Server: {e}{Colors.RESET}")
                print(f"{Colors.YELLOW}Tip: Ensure your local AI server (LM Studio / Ollama) is running with CORS and API enabled.{Colors.RESET}\n")
                chat_history.pop()
                continue

            chat_history.append({"role": "assistant", "content": ai_response})

            code_snippet = extract_python_code(ai_response)

            if code_snippet:
                explanation = ai_response.split("```")[0].strip()
                if explanation:
                    print(f"\n{Colors.CYAN}{Colors.BOLD}AI Assistant:{Colors.RESET}\n{explanation}\n")
                
                print(f"{Colors.YELLOW}{Colors.BOLD}┌── Generated Python Action Code for {office_mgr.active_app_type} ─────────┐{Colors.RESET}")
                for line in code_snippet.split('\n'):
                    print(f"{Colors.YELLOW}│ {Colors.RESET}{line}")
                print(f"{Colors.YELLOW}{Colors.BOLD}└─────────────────────────────────────────────────────────────┘{Colors.RESET}\n")

                if AUTO_EXECUTE:
                    print(f"{Colors.GREEN}[Auto-Executing action directly on Microsoft {office_mgr.active_app_type}...]{Colors.RESET}")
                    execute_generated_code(code_snippet, office_mgr, ai_client, chat_history)
                else:
                    confirm = input(f"{Colors.BOLD}Execute this action on active {office_mgr.active_app_type}? (Y/N): {Colors.RESET}").strip()
                    if confirm.lower() in ['y', 'yes']:
                        execute_generated_code(code_snippet, office_mgr, ai_client, chat_history)
                    else:
                        print(f"{Colors.YELLOW}Action execution skipped by user.{Colors.RESET}\n")
            else:
                print(f"\n{Colors.CYAN}{Colors.BOLD}AI Assistant:{Colors.RESET}\n{ai_response}\n")

        except KeyboardInterrupt:
            print(f"\n{Colors.YELLOW}\nExiting Un-Intels...{Colors.RESET}")
            break
        except Exception as e:
            print(f"\n{Colors.RED}Unexpected Error: {e}{Colors.RESET}\n")

if __name__ == '__main__':
    main()
