import os
import sys
import asyncio
import logging
import json
import time
import math
import hashlib
import zlib
import re
import difflib
import threading
import argparse
import subprocess
import ast
import base64
import secrets
import concurrent.futures
from datetime import datetime
from threading import Timer
from collections import Counter, deque
from typing import Any, List, Dict, Optional

# Multi-Language AST Support
try:
    from tree_sitter_languages import get_language, get_parser
    TREE_SITTER_AVAILABLE = True
except ImportError:
    TREE_SITTER_AVAILABLE = False

# MCP & Web Imports
from mcp.server.fastmcp import FastMCP
from dotenv import load_dotenv
from supabase import create_client, Client
from groq import Groq
import uvicorn
from starlette.responses import JSONResponse
from starlette.requests import Request
import httpx

# Configure logging to strictly use stderr to prevent polluting the JSON-RPC stdout stream
logging.basicConfig(
    level=logging.WARNING,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    stream=sys.stderr
)
logger = logging.getLogger("DalexorBridge")
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from fastapi.middleware.cors import CORSMiddleware

# Load local environment if it exists (Priority: Local .env > Shell Env)
load_dotenv(override=True)

# --- CONFIGURATION & AUTH (STATELESS) ---

def load_global_config():
    """Load configuration from global user config file."""
    import json
    config_file = os.path.join(os.path.expanduser("~/.dalexor"), "config.json")
    if os.path.exists(config_file):
        try:
            with open(config_file, 'r') as f:
                return json.load(f)
        except Exception as e:
            print(f"[!] Warning: Failed to load global config: {e}")
    return {}



# Load Global Config
GLOBAL_CONFIG = load_global_config()

# Try to load API KEY from Env -> Global Config
DX_API_KEY = os.getenv("DX_API_KEY") or os.getenv("DALEXORMI_API_KEY") or GLOBAL_CONFIG.get("api_key")

CLOUD_URL = os.getenv("DALEXORMI_URL", "https://api.dalexor.com")
DX_SOVEREIGN = os.getenv("DX_SOVEREIGN", GLOBAL_CONFIG.get("sovereign", "false")).lower() == "true"
DX_TEAM_SECRET = (os.getenv("DX_TEAM_SECRET") or GLOBAL_CONFIG.get("team_secret") or "").strip()

# Set globals for project context if available
if not os.getenv("DX_PROJECT_ID") and GLOBAL_CONFIG.get("project_id"):
    os.environ["DX_PROJECT_ID"] = GLOBAL_CONFIG.get("project_id")
    
if not os.getenv("DX_PROJECT_NAME") and GLOBAL_CONFIG.get("project_name"):
    os.environ["DX_PROJECT_NAME"] = GLOBAL_CONFIG.get("project_name")


# --- NEURAL VAULT (E2EE) ---
class NeuralVault:
    @staticmethod
    def get_key(secret: str):
        import hashlib, base64
        hasher = hashlib.sha256()
        hasher.update(secret.encode())
        return base64.urlsafe_b64encode(hasher.digest())

# Removed NeuralVault.lock: Encryption is now handled by the Cloud Brain.

    @staticmethod
    def unlock(blob: str, secret: str) -> str:
        if not blob or not secret: return blob
        
        # 🛡️ VAULT S1: AESGCM (Server-Side Architecture)
        if str(blob).startswith("vault_s1:"):
            try:
                from cryptography.hazmat.primitives.ciphers.aead import AESGCM
                raw_b64 = blob.replace("vault_s1:", "").strip()
                # Repair padding
                while len(raw_b64) % 4 != 0: raw_b64 += "="
                
                import base64, hashlib
                combined = base64.b64decode(raw_b64)
                iv = combined[:12]
                ciphertext = combined[12:]
                key = hashlib.sha256(secret.encode()).digest()
                aesgcm = AESGCM(key)
                return aesgcm.decrypt(iv, ciphertext, None).decode()
            except Exception as e:
                return f"[Neural Vault: S1 Decryption Failed ({str(e)})]"

        if not str(blob).startswith("vault_v1:"): return blob
        try:
            from cryptography.fernet import Fernet
            import base64
            cipher_text = blob.replace("vault_v1:", "").strip()
            
            # 🛡️ NOVEL BASE64 RECOVERY: Repair padding
            while len(cipher_text) % 4 != 0:
                cipher_text += "="
                
            f = Fernet(NeuralVault.get_key(secret))
            return f.decrypt(cipher_text.encode()).decode()
        except:
            return "[Neural Vault: Decryption Failed (Check DX_TEAM_SECRET)]"

# ... (Supabase/Groq init logic remains same) ...
# These are used by the CLOUD BRAIN (Railway) to talk to the DB/AI
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_SERVICE_ROLE = os.getenv("SUPABASE_SERVICE_ROLE_KEY") or os.getenv("SUPABASE_KEY")
GROQ_API_KEY = os.getenv("GROQ_API_KEY")

# Detection: Are we running on Railway?
IS_CLOUD = os.getenv("RAILWAY_ENVIRONMENT") == "true" or os.getenv("RAILWAY_STATIC_URL") is not None or os.getenv("VPS_MODE") == "true"

# Shared Constants (Immutable base)
HARD_IGNORE = {'.git', 'node_modules', '__pycache__', '.env', 'dist', 'build', '.next', '.dalexor', '.vscode', '.idea', 'venv', '.pyc', '.egg-info', '.pytest_cache', '.mypy_cache', '.key', '.pem', '.crt', '.bak', '.swp', 'data.sqlite', 'db.sqlite3'}
IGNORE_LIST = set(HARD_IGNORE)

def fetch_ignore_patterns(quiet=False):
    """Dynamically fetch exclusion patterns from the Global Brain (Org + Project)."""
    global DX_API_KEY
    api_key = DX_API_KEY or os.getenv("DX_API_KEY") or os.getenv("DALEXORMI_API_KEY") 
    project_id = os.getenv("DX_PROJECT_ID")
    
    if not api_key: return
    
    import requests
    headers = {"X-DX-Key": api_key}
    try:
        # 1. Fetch Organization Patterns
        resp = requests.get(f"{CLOUD_URL}/validate", headers=headers, timeout=5)
        if resp.status_code == 200:
            org_data = resp.json().get("organization", {})
            patterns = org_data.get("global_ignore_patterns", [])
            if patterns:
                for p in patterns: 
                    if p: IGNORE_LIST.add(p.strip())
            
        # 2. Fetch Project-Specific Patterns
        if project_id:
            proj_resp = requests.get(f"{CLOUD_URL}/projects", headers=headers, timeout=5)
            if proj_resp.status_code == 200:
                projects = proj_resp.json().get("projects", [])
                p_match = next((p for p in projects if p['id'] == project_id), None)
                if p_match and p_match.get("ignore_patterns"):
                    for p in p_match["ignore_patterns"]: 
                        if p: IGNORE_LIST.add(p.strip())
        
        if len(IGNORE_LIST) > len(HARD_IGNORE) and not quiet:
            custom_count = len(IGNORE_LIST) - len(HARD_IGNORE)
            # Use stderr for status logs to avoid corrupting the MCP stdio stream
            sys.stderr.write(f"🧬 [Sovereign Guardrails] {custom_count} custom patterns active: {', '.join(list(IGNORE_LIST - HARD_IGNORE))}\n")
    except Exception as e:
        if not quiet:
            sys.stderr.write(f"[*] Guardrail Load Fail: {e}\n")

def is_ignored(path):
    """Unified exclusion logic using Glob patterns and path analytics. 
       Checked against relative and absolute paths to handle 'in-folder' execution."""
    import fnmatch
    if not path: return False
    
    filename = os.path.basename(path)
    norm_path = path.replace("\\", "/").rstrip("/")
    abs_path = os.path.abspath(path).replace("\\", "/").rstrip("/")
    
    # We don't ignore the current directory if it's explicitly synced as "."
    # but we DO ignore contents if the path hints at an ignored parent.
    
    for pattern in IGNORE_LIST:
        pattern = pattern.strip().replace("\\", "/").rstrip("/")
        if not pattern: continue
        
        # 1. Exact Filename/Folder Match
        if filename == pattern: return True
        
        # 2. Glob Match on filename
        if fnmatch.fnmatch(filename, pattern): return True
        
        # 3. Path Segment Match (checks if any part of the path is the ignored folder)
        # We check both relative and absolute to catch cases where user is INSIDE the folder.
        segments_rel = norm_path.split("/")
        segments_abs = abs_path.split("/")
        
        if pattern in segments_rel or pattern in segments_abs:
            return True
            
        # 4. Glob Match on full relative path
        if fnmatch.fnmatch(norm_path, pattern) or fnmatch.fnmatch(norm_path, f"*/{pattern}/*"):
            return True

        # 5. Guardrail: If pattern is a substring of a path segment (e.g. pattern "reports" in "my_reports")
        if any(pattern in s for s in segments_rel if s and s != ".") or any(pattern in s for s in segments_abs if s and s != "."):
            # Only block if it looks like a directory match or specific file match
            return True
        
    return False

# --- INITIALIZE DATABASE ONLY IF IN CLOUD ---
supabase_admin: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_ROLE) if (IS_CLOUD and SUPABASE_URL and SUPABASE_SERVICE_ROLE) else None
groq_client = Groq(api_key=GROQ_API_KEY) if (IS_CLOUD and GROQ_API_KEY) else None

# --- ENTROPY ENGINE ---
class SovereignEntropyEngine:
    @staticmethod
    def calculate(content, file_path=""):
        if not content or len(content) < 10: return 0.0
        
        # Layer 1: Token-based Shannon Entropy (Semantic Density)
        tokens = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', content)
        if not tokens: return 0.5
        
        counts = Counter(tokens)
        total_tokens = len(tokens)
        shannon = -sum((count/total_tokens) * math.log2(count/total_tokens) for count in counts.values())
        
        # Layer 2: Autonomous Gravity (Structural Signature Analysis)
        gravity = 1.0
        
        # A. Identity Affinity: If tokens match the file name, the file is describing ITSELF (High Signal).
        if file_path:
            file_name = os.path.basename(file_path).split('.')[0].lower()
            if len(file_name) > 2:
                token_set = {t.lower() for t in tokens}
                if file_name in token_set:
                    gravity = max(gravity, 3.5)
        
        # B. Symbol Complexity Boost: Any token that looks like a Class, Function, or Constant.
        # PascalCase, camelCase, or UPPER_SNAKE
        complex_symbols = [t for t in tokens if re.search(r'[a-z][A-Z]', t) or re.match(r'^[A-Z][a-z]+[A-Z]', t) or (re.match(r'^[A-Z_]{4,}$', t))]
        symbol_density = len(complex_symbols) / total_tokens
        gravity += (symbol_density * 5.0)
        
        # Layer 3: Structural Multiplier (Logic density check)
        structure_multiplier = 1.0
        logic_patterns = [
            r'def\s+\w+', r'async\s+def', r'class\s+\w+',
            r'return\s+', r'if\s+.*:', r'import\s+', r'@\w+\(',
            r'\{[\s\S]*\}', r'supabase'
        ]
        
        for pattern in logic_patterns:
            matches = re.findall(pattern, content)
            if matches:
                structure_multiplier += (len(matches) * 0.20)
        
        # Layer 4: Noise Penalty (Repetitive boilerplate reduction)
        unique_tokens = len(counts)
        lexical_diversity = unique_tokens / total_tokens
        if lexical_diversity < 0.2 and total_tokens > 30:
            structure_multiplier *= 0.4
            
        # Final Sovereign Calculation
        score = (shannon * gravity * structure_multiplier) / 1.5
        return round(min(score, 10.0), 2)

# --- TRANSACTION MEMORY ---
class TransactionMemory:
    def __init__(self, cloud_callback):
        self.buffer = deque(maxlen=50)
        self.cloud_callback = cloud_callback
        self.lock = threading.Lock()
        self.flush_timer = None

    def add(self, file_path, diff, entropy, synapses, symbols, force_seal=False, full_state=None, metadata_extras=None):
        with self.lock:
            item = {"file": file_path, "diff": diff, "entropy": entropy, "synapses": synapses, "symbols": symbols, "ts": time.time(), "seal": force_seal, "full_state": full_state}
            if metadata_extras:
                item.update(metadata_extras)
            self.buffer.append(item)
            
            if force_seal or len(self.buffer) >= 5:
                self._flush()
            else:
                if self.flush_timer: self.flush_timer.cancel()
                self.flush_timer = Timer(10.0, self._flush)
                self.flush_timer.start()

    def flush_all(self, commit_message: str = None):
        """Force immediate flush of all pending items, marking them as seals."""
        with self.lock:
            if not self.buffer:
                print("[!] No pending evolution detected. (Use 'dx seal' for full project snapshot)")
                return
            
            for item in self.buffer:
                item['seal'] = True
                if commit_message:
                    item['commit_message'] = commit_message
                
            label = f": {commit_message}" if commit_message else ""
            print(f"[!] Sealing Milestone{label}: Transmitting {len(self.buffer)} pending evolutionary atoms...")
        self._flush()

    def _flush(self):
        with self.lock:
            if not self.buffer: return
            batch = list(self.buffer)
            self.buffer.clear()
            payloads_to_send = []

            for item in batch:
                content = item['diff']
                file_path = item['file']
                entropy = item['entropy']
                is_seal = item.get('seal', False)
                commit_message = item.get('commit_message')
                f_state = item.get('full_state') or content
                c_hash = hashlib.sha256(f_state.encode()).hexdigest()

                # Build summary — commit message takes priority over generic label
                if commit_message:
                    summary = f"{commit_message} — {os.path.basename(file_path)}"
                elif is_seal:
                    summary = f"MILESTONE: {os.path.basename(file_path)} Sealed"
                else:
                    summary = f"Evolutionary Shift in {os.path.basename(file_path)}"
                
                payload = {
                    "content": scrub_secrets(content[:400000000]) if not content.startswith("vault_") else content,
                    "entropy_score": 10.0 if is_seal else round(entropy, 2),
                    "atom_type": "evolution_event",
                    "source": "sentinel",
                    "synapses": item.get('synapses', []),
                    "symbols": item.get('symbols', []),
                    "is_sovereign": DX_SOVEREIGN,
                    "request_server_encryption": DX_SOVEREIGN,
                    "new_hash": item.get("new_hash") or c_hash,
                    "metadata": {
                        "file_path": file_path.replace("\\", "/"),
                        "file_name": os.path.basename(file_path),
                        "content_hash": c_hash,
                        "summary": summary,
                        "commit_message": commit_message,
                        "synapses": item.get('synapses', []),
                        "symbols": item.get('symbols', []),
                        "dx_seal": is_seal,
                        "sovereign_full_state": scrub_secrets(item.get('full_state')) if item.get('full_state') else None
                    }
                }
                payloads_to_send.append(payload)

        # Send sequentially — each push must complete before the next so
        # parent_hash chains are respected and conflicts are detected correctly.
        def _send_sequential(payloads):
            for p in payloads:
                self.cloud_callback(p)

        if payloads_to_send:
            threading.Thread(target=_send_sequential, args=(payloads_to_send,), daemon=True).start()

# --- UTILITY FUNCTIONS ---
def scrub_secrets(text):
    """Remove potential secrets from text before uploading while preserving syntax."""
    # Intelligent patterns targeting quoted strings and specific key formats
    patterns = [
        # 1. Broad Variable Assignment Pattern
        # Matches any variable (camelCase, snake_case, PascalCase) containing security keywords
        # Also handles JSON/YAML style colons and preserves original whitespace and quotes.
        # Keywords: key, secret, token, password, credential, auth, access, private, pwd, pass, login, session, cookie, jwt, vault
        (r'(?i)([a-z0-9_-]*(?:api|token|secret|password|auth|access|private|credential|pwd|pass|login|key|id|sig|salt|jwt|session|cookie|vault)[a-z0-9_-]*)(\s*[:=]\s*)(["\'])([^"\'\s]{8,})\3', r'\1\2\3***REDACTED***\3'),
        
        # 2. Cloud Platform & specific formats - high-confidence regex
        (r'(?i)(AKIA[0-9A-Z]{16,})', 'AKIA****************'), # AWS Key ID
        (r'(?i)(AIza[0-9A-Za-z\\-_]{35,})', 'AIza****************'), # GCP API Key
        (r'(?i)(AccountKey=[a-zA-Z0-9+/]{80,}==)', 'AccountKey=****************'), # Azure Storage Key
        (r'(?i)sk_live_[0-9a-zA-Z]{20,}', 'sk_live_***REDACTED***'), # Stripe
        (r'(?i)ghp_[0-9a-zA-Z]{30,}', 'ghp_***REDACTED***'), # GitHub
        
        # 3. Headers & Tokens
        (r'Bearer\s+[A-Za-z0-9\-._~+/]+=*', 'Bearer ***REDACTED***'),
        (r'(?i)eyJ[a-zA-Z0-9\._\-]{30,}', 'eyJ********'), # JWTs
        
        # 4. Long Random Strings in Assignments (Heuristic Fallback)
        # Catches strings > 32 chars assigned to something, which usually indicates a key
        (r'([a-zA-Z0-9_-]+)\s*([:=])\s*(["\'])([a-zA-Z0-9+/]{32,})\3', r'\1\2\3***REDACTED***\3'),
    ]
    for pattern, replacement in patterns:
        text = re.sub(pattern, replacement, text)
    return text

def print_banner(email="User", plan="Identity Verified"):
    """Print welcome banner with user info."""
    print("\n" + "="*60)
    print(" DALEXOR MI : NEURAL INTELLIGENCE")
    print("="*60)
    print(f" User: {email}")
    print(f" Plan: {plan}")
    print("="*60 + "\n")


def interactive_verification(force_prompt=False):
    """Verify identity with the Global Brain using environment or interactive prompt."""
    global DX_API_KEY, DX_TEAM_SECRET
    if not sys.stdin.isatty(): return (DX_API_KEY, "System", "Automated")
    
    current_key = DX_API_KEY
    if not current_key or force_prompt:
        print("\n" + "="*60)
        print(" DALEXOR MI : SECURE HANDSHAKE")
        print("="*60)
        current_key = input("\n[*] Enter API Key: ").strip()
        if not current_key: sys.exit(1)
        DX_API_KEY = current_key

    print("\n[*] Connecting to Global Brain...")
    try:
        import requests
        headers = {"X-DX-Key": current_key}
        response = requests.get(f"{CLOUD_URL}/validate", headers=headers, timeout=30)
        
        if response.status_code == 200:
            data = response.json()
            user_data = data.get("user", {})
            email = user_data.get('email', 'User')
            display_plan = data.get("subscription", {}).get("display", "Identity Verified")
            
            # CRITICAL: Use personal_plan (user's own subscription) for vault gating.
            # access_tier (org-aware effective plan) is for feature flags only.
            personal_plan = (
                user_data.get("personal_plan")
                or data.get("subscription", {}).get("personal_plan")
                or user_data.get("plan", "free")
            ).lower()
            
            print(f"[+] Identity Confirmed: {email}")
            print(f"[*] Subscription: {display_plan}")

            # 🛡️ SOVEREIGN MODE ACTIVATION — strictly based on PERSONAL plan only.
            # DB internal plan codes: free/personal, users/hobby, standard(=Pro display), pro(=Expert display), enterprise(=Startup display), sovereign
            # Vault-eligible: pro (Expert), enterprise (Startup), sovereign — NOT standard (Pro display)
            sovereign_codes = ["pro", "professional", "expert", "enterprise", "startup", "sovereign"]
            restricted_codes = ["free", "personal", "users", "hobby", "standard"]
            
            is_sovereign_plan = any(c in personal_plan for c in sovereign_codes) and not any(c in personal_plan for c in restricted_codes)
            
            global DX_SOVEREIGN
            if is_sovereign_plan:
                DX_SOVEREIGN = True
                print(f"[*] Privacy Mode: ACTIVATED (End-to-End Encryption Enabled)")
                
                # 🔐 Prompt for Team Secret if not already set
                if not DX_TEAM_SECRET or force_prompt:
                    print("\n" + "="*60)
                    print(" NEURAL VAULT : E2EE TEAM SECRET")
                    print("="*60)
                    team_secret_input = input("\n[*] Enter Team Secret (E2EE Key): ").strip()
                    if team_secret_input:
                        DX_TEAM_SECRET = team_secret_input
                        print("[+] Team Secret configured for Neural Vault encryption.")
            else:
                DX_SOVEREIGN = False
            
            # Return personal_plan as the 3rd element so select_project gates vault correctly
            return (current_key, email, personal_plan)
        else:
            print(f"[!] Verification Failed: {response.json().get('error', 'Invalid Key')}")
            sys.exit(1)
    except Exception as e:
        print(f"[!] Error: {e}")
        sys.exit(1)

def save_to_env(key, project_id=None, project_name=None, team_secret=None):
    """Save configuration to global config file."""
    import json
    
    # Use global config directory
    config_dir = os.path.expanduser("~/.dalexor")
    config_file = os.path.join(config_dir, "config.json")
    
    # Create directory if it doesn't exist
    os.makedirs(config_dir, exist_ok=True)
    
    # Read existing config
    config = {}
    if os.path.exists(config_file):
        try:
            with open(config_file, 'r') as f:
                config = json.load(f)
        except:
            pass
    
    # Update with new values
    config['api_key'] = key
    if project_id:
        config['project_id'] = project_id
        config['last_project_id'] = project_id
    if project_name:
        config['project_name'] = project_name
    if team_secret:
        config['team_secret'] = team_secret
    
    # Save Sovereign status
    config['sovereign'] = str(DX_SOVEREIGN).lower()
    
    config['cloud_url'] = CLOUD_URL
    
    # Save config
    with open(config_file, 'w') as f:
        json.dump(config, f, indent=2)
    
    print(f"[+] Configuration saved to {config_file}")

def select_project(key, plan="free"):
    """Fetch and select a project, pinning it on the server for stateless sessions."""
    import requests
    
    # Update global API key to match the one used in dx init
    global DX_API_KEY
    DX_API_KEY = key
    
    headers = {"X-DX-Key": key}
    print("\n[*] Fetching project blueprints...")
    try:
        proj_res = requests.get(f"{CLOUD_URL}/projects", headers=headers, timeout=30)
        project_id = None
        project_name = "Default"
        
        if proj_res.status_code == 200:
            projects = proj_res.json().get("projects", [])
            if projects:
                print(f"\n[?] Found {len(projects)} existing project universes:")
                for idx, p in enumerate(projects):
                    print(f"   {idx + 1}. {p['name']} ({p.get('status', 'active')})")
                print(f"   {len(projects) + 1}. [Create New Project]")
                
                choice = input("\n[>] Select Project: ").strip()
                try:
                    c_idx = int(choice) - 1
                    if 0 <= c_idx < len(projects):
                        project_id = projects[c_idx]["id"]
                        project_name = projects[c_idx]["name"]
                    else:
                        project_name = input("[>] Enter New Project Name: ").strip()
                except:
                    project_id = None 
                    project_name = input("[>] Enter New Project Name: ").strip()
            else:
                project_name = input("[>] No projects found. Enter Project Name: ").strip()
        
        if not project_id:
            create_res = requests.post(f"{CLOUD_URL}/projects", json={"name": project_name}, headers=headers, timeout=30)
            if create_res.status_code == 200:
                project_id = create_res.json().get("project", {}).get("id")
                print(f"[+] Project '{project_name}' created and assigned.")
        
        # 🌩️ PIN PROJECT ON SERVER (No local files!)
        requests.post(f"{CLOUD_URL}/select-project", json={"project_id": project_id}, headers=headers, timeout=30)
        os.environ["DX_PROJECT_ID"] = project_id
        os.environ["DX_PROJECT_NAME"] = project_name
        print(f"[+] Context Pinned to Cloud: {project_name}")

        # --- E2EE NEURAL VAULT KEY MANAGEMENT ---
        print("\n" + "-"*60)
        print(" NEURAL VAULT : END-TO-END ENCRYPTION")
        print("-"*60)
        
        # Vault-eligible internal DB codes: pro (Expert), enterprise (Startup), sovereign
        # NOT eligible: free, personal, users, hobby, standard (which displays as 'Pro')
        vault_codes = ["pro", "professional", "expert", "enterprise", "startup", "sovereign"]
        is_vault_eligible = any(p in plan.lower() for p in vault_codes) and not any(
            r in plan.lower() for r in ["free", "personal", "users", "hobby", "standard"]
        )
        is_restricted_plan = not is_vault_eligible
        if is_restricted_plan:
            print("[!] PLAN RESTRICTION: Neural Vault (E2EE) is an Expert/Sovereign feature.")
            print(f"[!] Your '{plan}' plan uses Standard Encryption (TLS/AES).")
        else:
            # Prioritize user-entered team secret, then fall back to server
            global DX_TEAM_SECRET
            if not DX_TEAM_SECRET:
                # Secrets come from server only if not manually entered
                all_proj_res = requests.get(f"{CLOUD_URL}/projects", headers=headers, timeout=30).json()
                selected_p = next((p for p in all_proj_res.get("projects", []) if p['id'] == project_id), {})
                team_secret = selected_p.get("team_secret")
                if team_secret:
                    DX_TEAM_SECRET = team_secret
                    os.environ["DX_TEAM_SECRET"] = team_secret
            else:
                # User manually entered the secret
                os.environ["DX_TEAM_SECRET"] = DX_TEAM_SECRET
            
            if DX_TEAM_SECRET:
                print(f"[*] Neural Vault Key active for this project (Cloud Sync).")
                
                # 🌩️ UPLOAD SECRET TO GLOBAL BRAIN (E2EE Handshake)
                try:
                    requests.post(f"{CLOUD_URL}/projects/{project_id}/secret", json={"secret": DX_TEAM_SECRET}, headers=headers, timeout=10)
                    print("[+] Neural Vault Handshake Complete: Cloud Sync Enabled.")
                except Exception as e:
                    print(f"[*] Warning: Key Sync delayed: {e}")
        
        print(f"[+] Project {project_name} Ready. CLI is operating in Stateless Cloud Mode.")
        
        # Save to .env file
        save_to_env(key, project_id, project_name, os.environ.get("DX_TEAM_SECRET"))

        return project_id, project_name
    except Exception as e:
        print(f"[!] Project Selection Error: {e}")
        return None, "Default"

# ─────────────────────────────────────────────────────────────────────────────
# CLOUD-NATIVE REVIEW QUEUE
# All pending review state lives in the server's pending_reviews table.
# No local files, no stale JSON, no process-boundary issues.
# Members always re-fetch live content from /file-snapshot at accept time.
# ─────────────────────────────────────────────────────────────────────────────

_PAUSED_PATHS: set = set()
_PAUSED_PATHS_LOCK = threading.Lock()

def _cloud_get_pending() -> dict:
    """Fetch this user's personal review queue from the cloud. Returns dict keyed by file_path."""
    import requests as _r
    try:
        res = _r.get(f"{CLOUD_URL}/review/pending", headers={"X-DX-Key": DX_API_KEY}, timeout=10)
        if res.status_code == 200:
            return res.json()
    except Exception as e:
        logger.warning(f"Failed to fetch review queue: {e}")
    return {}

def _cloud_accept(review_id: str = None, file_path: str = None) -> dict:
    """Tell the server this user has accepted a review. Returns {ok, file_path, content_hash, atom_id}."""
    import requests as _r
    body = {}
    if review_id: body["review_id"] = review_id
    if file_path: body["file_path"] = file_path
    try:
        res = _r.post(f"{CLOUD_URL}/review/accept", json=body, headers={"X-DX-Key": DX_API_KEY}, timeout=10)
        return res.json()
    except Exception as e:
        return {"error": str(e)}

def _cloud_reject(review_id: str = None, file_path: str = None) -> dict:
    """Tell the server this user has rejected a review."""
    import requests as _r
    body = {}
    if review_id: body["review_id"] = review_id
    if file_path: body["file_path"] = file_path
    try:
        res = _r.post(f"{CLOUD_URL}/review/reject", json=body, headers={"X-DX-Key": DX_API_KEY}, timeout=10)
        return res.json()
    except Exception as e:
        return {"error": str(e)}

def _cloud_fetch_content(file_path: str, project_id: str = None) -> tuple:
    """
    Fetch the LIVE content of a file from /file-snapshot.
    Always called at accept time — never from a cached local copy.
    Returns (content, is_encrypted, content_hash).
    """
    import requests as _r
    params = {"file_path": file_path}
    if project_id: params["project_id"] = project_id
    try:
        res = _r.get(f"{CLOUD_URL}/file-snapshot", headers={"X-DX-Key": DX_API_KEY}, params=params, timeout=15)
        if res.status_code == 200:
            d = res.json()
            content = d.get("content", "")
            is_enc = d.get("is_encrypted", False) or str(content).startswith("vault_")
            if is_enc:
                active_secret = os.getenv("DX_TEAM_SECRET") or DX_TEAM_SECRET
                if active_secret:
                    content = NeuralVault.unlock(content, active_secret)
            return content, is_enc, d.get("content_hash", "")
        return None, False, None
    except Exception as e:
        logger.warning(f"Failed to fetch live content for {file_path}: {e}")
        return None, False, None


class CodeChangeHandler(FileSystemEventHandler):
    def __init__(self, tx_memory):
        self.tx_memory = tx_memory
        self.last_hashes = self.load_hashes()
        self.content_cache = {}

    def pause_path(self, file_path: str):
        """Pause Sentinel for this path so accept/reject writes don't trigger re-upload."""
        abs_path = os.path.abspath(file_path)
        with _PAUSED_PATHS_LOCK:
            _PAUSED_PATHS.add(abs_path)

    def resume_path(self, file_path: str):
        """Resume Sentinel for this path."""
        abs_path = os.path.abspath(file_path)
        with _PAUSED_PATHS_LOCK:
            _PAUSED_PATHS.discard(abs_path)

    def load_hashes(self):
        """Load known file hashes from disk."""
        path = os.path.join(".dalexor", "states.json")
        try:
            if os.path.exists(path):
                with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                    data = json.load(f)
                    if isinstance(data, dict):
                        return data.get("hashes", {})
        except: pass
        return {}

    def save_hashes(self):
        """Persist hashes to disk for future session continuity."""
        dalexor_dir = ".dalexor"
        # ✅ FIX: Guard against .dalexor existing as a file (same WinError 183 class of bug)
        if os.path.exists(dalexor_dir) and not os.path.isdir(dalexor_dir):
            try: os.remove(dalexor_dir)
            except: pass
        if not os.path.exists(dalexor_dir):
            try: os.makedirs(dalexor_dir)
            except: pass
        path = os.path.join(dalexor_dir, "states.json")
        try:
            with open(path, 'w', encoding='utf-8') as f:
                json.dump({"hashes": self.last_hashes, "ts": time.time()}, f)
        except: pass

    def on_modified(self, event): self._handle_event(event)
    def on_created(self, event): self._handle_event(event)

    def _handle_event(self, event):
        if event.is_directory: return
        if is_ignored(event.src_path): return
        
        abs_path = os.path.abspath(event.src_path)
        with _PAUSED_PATHS_LOCK:
            if abs_path in _PAUSED_PATHS: return
        
        # Load fresh hashes from disk to avoid stale parent_hash in multi-process scenarios
        self.last_hashes = self.load_hashes()
        
        # Normalize Windows paths to Forward Slashes for Lineage Parity
        src_path = event.src_path.replace('\\', '/')
        
        # Neural Pulse Feedback
        print(f"[*] Neural Pulse: {os.path.basename(src_path)} detected.")
        
        # Windows Grace Period: Let the IDE finish writing the file
        time.sleep(0.2)
        
        # Instant Presence report (Presence Parity)
        self.report_presence(src_path)
        # Debounce/Throttle at Sentinel level too
        self.process_change(src_path)

    def report_presence(self, file_path):
        import requests
        try:
            # Dynamically fetch key to ensure validity
            key = os.getenv("DX_API_KEY") or os.getenv("DALEXORMI_API_KEY") or DX_API_KEY
            project_id = os.getenv("DX_PROJECT_ID")
            if not key: return
            headers = {"X-DX-Key": key}
            # Silent fire-and-forget presence pulse
            requests.post(f"{CLOUD_URL}/presence", json={"file_path": file_path, "project_id": project_id}, headers=headers, timeout=1.0)
        except Exception as e:
            # Presence pulse is non-critical, but we log the debug signal
            logger.debug(f"Presence pulse failed: {e}")
            pass

    def extract_synapses(self, content, current_file_name="", project_catalog=None):
        """Atomic Synapse Engine V7: Tree-Sitter & Greedy Dispatcher."""
        if not content: return []
        found = set()
        ext = os.path.splitext(current_file_name)[1].lower()

        # 0. NATIVE PYTHON AST (Built-in)
        if ext == '.py':
            try:
                tree = ast.parse(content)
                for node in ast.walk(tree):
                    if isinstance(node, ast.Import):
                        for alias in node.names:
                            found.add(alias.name.split('.')[0])
                            # Capture specific sub-modules or aliases
                            if '.' in alias.name: found.add(alias.name.split('.')[-1])
                    elif isinstance(node, ast.ImportFrom):
                        if node.module:
                            found.add(node.module.split('.')[0])
                            # Capture leaf module name
                            found.add(node.module.split('.')[-1])
                        # Capture specific symbols imported (e.g. from x import Y)
                        for alias in node.names:
                            found.add(alias.name)
            except: pass

        # 1. TREE-SITTER RECOVERY (For other languages)
        elif TREE_SITTER_AVAILABLE:
            lang_map = {
                '.js': 'javascript', '.jsx': 'javascript',
                '.ts': 'typescript', '.tsx': 'tsx',
                '.go': 'go', '.rs': 'rust', '.rb': 'ruby',
                '.cpp': 'cpp', '.h': 'cpp', '.cs': 'c_sharp',
                '.java': 'java', '.php': 'php'
            }
            lang_name = lang_map.get(ext)
            if lang_name:
                try:
                    parser = get_parser(lang_name)
                    tree = parser.parse(bytes(content, "utf8"))
                    
                    # Custom Query for Imports/Requires
                    query_map = {
                        'javascript': '(import_statement source: (string) @src) (call_expression function: (identifier) @func arguments: (arguments (string) @src) (#eq? @func "require"))',
                        'typescript': '(import_statement source: (string) @src) (call_expression function: (identifier) @func arguments: (arguments (string) @src) (#eq? @func "require"))',
                        'tsx': '(import_statement source: (string) @src)',
                        'go': '(import_spec path: (string) @src)',
                        'rust': '(use_declaration argument: (use_path) @src)',
                        'php': '(include_expression path: (string) @src) (require_expression path: (string) @src)',
                    }
                    
                    query_str = query_map.get(lang_name)
                    if query_str:
                        language = get_language(lang_name)
                        query = language.query(query_str)
                        captures = query.captures(tree.root_node)
                        for node, tag in captures:
                            if tag == 'src':
                                name = node.text.decode('utf8').strip('\'"`').split('/')[-1]
                                if name: found.add(name)
                except Exception as e:
                    logger.debug(f"Tree-Sitter Parse Error for {current_file_name}: {e}")

        # 2. FALLBACK/REFINED REGEX (If Tree-Sitter failed or not available)
        if not found and ext in ['.js', '.ts', '.jsx', '.tsx', '.cs', '.cpp', '.h', '.java', '.go', '.rs', '.rb']:
            patterns = [
                r'import\s+.*?\s+from\s+[\'"](.*?)[\'"]',
                r'require\([\'"](.*?)[\'"]\)',
                r'export\s+.*?\s+from\s+[\'"](.*?)[\'"]',
                r'using\s+([\w\.]+);',
                r'#include\s+[<"](.*)[>"]',
                r'use\s+([\w:]+);',
                # Captured specific curly brace imports in JS/TS
                r'import\s+\{(.*?)\}\s+from\s+[\'"](.*?)[\'"]'
            ]
            for p in patterns:
                matches = re.finditer(p, content)
                for m in matches:
                    raw = m.group(1)
                    # If multiple symbols in curly braces, split and add all
                    if '{' in p and ',' in raw:
                        for s in raw.split(','):
                            found.add(s.strip().split(' as ')[0])
                        found.add(raw.split('/')[-1].split('.')[0])

        # 3. HTML/WEB ANALYZER (New Sovereign Layer)
        elif ext == '.html':
            html_patterns = [
                r'src=["\'](.*?)["\']',
                r'href=["\'](.*?)["\']',
                r'action=["\'](.*?)["\']',
            ]
            for p in html_patterns:
                for match in re.findall(p, content):
                    # Robust path handling: Remove query params & hashes
                    clean_match = match.split('?')[0].split('#')[0]
                    # Get basename for catalog matching
                    name = clean_match.split('/')[-1].split('\\')[-1]
                    if name and '.' in name and len(name) > 2: 
                        found.add(name)

        # 4. UNIFIED CATALOG CROSS-CHECK (The Intelligence Layer)
        if project_catalog:
            # Strip comments before catalog matching to avoid "Comment Ghosts"
            clean_content = re.sub(r'//.*|/\*[\s\S]*?\*/|#.*|"""[\s\S]*?"""', '', content)
            tokens = set(re.findall(r'[A-Z][a-zA-Z0-9_]*|\b[a-zA-Z_]\w*\.[a-zA-Z0-9]+\b', clean_content))
            for t in tokens:
                if t in project_catalog and t != current_file_name:
                    found.add(t)

        system_exclude = {'os', 'sys', 'json', 'math', 'hashlib', 'time', 're', 'datetime', 'threading', 'logging', 'subprocess', 'ast', 'base64', 'secrets', 'react', 'vue', 'fs', 'path', 'http', 'https'}
        return [f for f in found if f.lower() not in system_exclude and f != current_file_name]

    def extract_symbols(self, content, file_path=""):
        """Sovereign Symbol Extraction: Multi-language semantic indexing."""
        if not content: return []
        symbols = set()
        ext = os.path.splitext(file_path)[1].lower()

        # 1. PYTHON NATIVE AST
        if ext == '.py':
            try:
                tree = ast.parse(content)
                for node in ast.walk(tree):
                    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): symbols.add(node.name)
            except: pass

        # 2. TREE-SITTER SYBMOL MINING
        elif TREE_SITTER_AVAILABLE:
            lang_map = {
                '.js': 'javascript', '.jsx': 'javascript',
                '.ts': 'typescript', '.tsx': 'tsx',
                '.go': 'go', '.rs': 'rust', '.rb': 'ruby',
                '.cpp': 'cpp', '.h': 'cpp', '.cs': 'c_sharp',
                '.java': 'java', '.php': 'php', '.kt': 'kotlin',
                '.swift': 'swift',
            }
            lang_name = lang_map.get(ext)
            if lang_name:
                try:
                    parser = get_parser(lang_name)
                    tree = parser.parse(bytes(content, "utf8"))
                    query_map = {
                        'javascript': '(function_declaration name: (identifier) @name) (class_declaration name: (identifier) @name) (variable_declarator name: (identifier) @name)',
                        'typescript': '(function_declaration name: (identifier) @name) (class_declaration name: (identifier) @name) (interface_declaration name: (type_identifier) @name) (type_alias_declaration name: (type_identifier) @name) (enum_declaration name: (identifier) @name)',
                        'tsx': '(function_declaration name: (identifier) @name) (class_declaration name: (identifier) @name)',
                        'go': '(function_declaration name: (identifier) @name) (type_spec name: (type_identifier) @name) (method_declaration name: (field_identifier) @name)',
                        'rust': '(function_item name: (identifier) @name) (struct_item name: (type_identifier) @name) (trait_item name: (identifier) @name) (enum_item name: (type_identifier) @name) (impl_item type: (type_identifier) @name)',
                        'ruby': '(method name: (identifier) @name) (class name: (constant) @name) (module name: (constant) @name)',
                        'java': '(method_declaration name: (identifier) @name) (class_declaration name: (identifier) @name) (interface_declaration name: (identifier) @name)',
                        'c_sharp': '(method_declaration name: (identifier) @name) (class_declaration name: (identifier) @name) (interface_declaration name: (identifier) @name)',
                        'php': '(function_definition name: (name) @name) (class_declaration name: (name) @name) (interface_declaration name: (name) @name)',
                        'kotlin': '(function_declaration (simple_identifier) @name) (class_declaration (type_identifier) @name)',
                        'swift': '(function_declaration name: (simple_identifier) @name) (class_declaration name: (type_identifier) @name)',
                    }
                    query_str = query_map.get(lang_name)
                    if query_str:
                        language = get_language(lang_name)
                        query = language.query(query_str)
                        captures = query.captures(tree.root_node)
                        for node, tag in captures:
                            symbols.add(node.text.decode('utf8'))
                except: pass

        # 3. FALLBACK REGEX SUITE — covers 25+ languages
        if not symbols:
            patterns = {
                '.py':  r'(?:async\s+)?(?:def|class)\s+([a-zA-Z_]\w*)',
                '.js':  r'(?:function|class|const|let|var)\s+([a-zA-Z_]\w*)',
                '.ts':  r'(?:function|class|interface|type|enum|const)\s+([a-zA-Z_]\w*)',
                '.tsx': r'(?:function|class|interface|const)\s+([a-zA-Z_]\w*)',
                '.jsx': r'(?:function|class|const)\s+([a-zA-Z_]\w*)',
                '.go':  r'(?:func|type)\s+(?:\([^)]+\)\s+)?([a-zA-Z_]\w*)',
                '.rs':  r'(?:pub\s+)?(?:fn|mod|struct|enum|trait|type|impl)\s+([a-zA-Z_]\w*)',
                '.rb':  r'(?:def|class|module)\s+([a-zA-Z_]\w*)',
                '.java':r'(?:public|private|protected)?\s*(?:static\s+)?(?:class|interface|enum|void|\w+)\s+([A-Z][a-zA-Z_]\w*)\s*[({]',
                '.kt':  r'(?:fun|class|interface|object|data\s+class)\s+([a-zA-Z_]\w*)',
                '.cs':  r'(?:public|private|protected|internal)?\s*(?:static\s+)?(?:class|interface|struct|enum|void|\w+)\s+([A-Z][a-zA-Z_]\w*)\s*[({]',
                '.cpp': r'(?:class|struct|void|[\w<>]+)\s+([a-zA-Z_]\w*)(?:\s*\(|\s*\{)',
                '.c':   r'(?:void|int|char|float|double|struct|typedef)\s+([a-zA-Z_]\w*)\s*[({;]',
                '.h':   r'(?:class|struct|void|[\w<>]+)\s+([a-zA-Z_]\w*)(?:\s*\(|\s*\{)',
                '.swift':r'(?:func|class|struct|enum|protocol)\s+([a-zA-Z_]\w*)',
                '.php': r'(?:function|class|interface|trait)\s+([a-zA-Z_]\w*)',
                '.sql': r'(?:CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE|TABLE|VIEW|INDEX|TRIGGER))\s+(?:IF\s+NOT\s+EXISTS\s+)?([a-zA-Z_]\w*)',
                '.lua': r'(?:local\s+)?function\s+([a-zA-Z_][\w.]*)',
                '.sh':  r'(?:function\s+)?([a-zA-Z_]\w*)\s*\(\s*\)',
                '.ex':  r'(?:def|defp|defmodule)\s+([a-zA-Z_]\w*)',
                '.exs': r'(?:def|defp|defmodule)\s+([a-zA-Z_]\w*)',
                '.scala':r'(?:def|class|object|trait)\s+([a-zA-Z_]\w*)',
                '.sol': r'(?:function|contract|modifier|event)\s+([a-zA-Z_]\w*)',
                '.tf':  r'(?:resource|module|variable|output)\s+"([^"]+)"',
            }
            p = patterns.get(ext)
            if p: symbols.update(re.findall(p, content, re.IGNORECASE if ext == '.sql' else 0))
            else: symbols.update(re.findall(r'(?:def|class|function|func|fn|struct|interface|type|module)\s+([a-zA-Z_]\w*)', content))
            
        return list(symbols)

    def extract_outline(self, content: str, file_path: str) -> str:
        """Extracts a skeletal outline of a file (Imports, Classes, Functions, Decorators)."""
        ext = os.path.splitext(file_path)[1].lower()
        
        # 🐍 Python (AST Based - Precise)
        if ext == '.py':
            try:
                tree = ast.parse(content)
                lines = []
                for node in ast.iter_child_nodes(tree):
                    # Imports
                    if isinstance(node, (ast.Import, ast.ImportFrom)):
                        try: lines.append(ast.unparse(node))
                        except: pass
                    # Classes
                    elif isinstance(node, ast.ClassDef):
                        # Class Decorators
                        for dec in node.decorator_list:
                            try: lines.append(f"@{ast.unparse(dec)}")
                            except: pass
                        # Class Definition
                        bases = f"({', '.join([ast.unparse(b) for b in node.bases])})" if node.bases else ""
                        lines.append(f"class {node.name}{bases}:")
                        # Scan class body for methods
                        for sub in node.body:
                            if isinstance(sub, (ast.FunctionDef, ast.AsyncFunctionDef)):
                                for sdec in sub.decorator_list:
                                    try: lines.append(f"    @{ast.unparse(sdec)}")
                                    except: pass
                                prefix = "async " if isinstance(sub, ast.AsyncFunctionDef) else ""
                                args = ast.unparse(sub.args) if hasattr(ast, 'unparse') else "..."
                                lines.append(f"    {prefix}def {sub.name}({args}): ...")
                    # Top-level Functions
                    elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                        for dec in node.decorator_list:
                            try: lines.append(f"@{ast.unparse(dec)}")
                            except: pass
                        prefix = "async " if isinstance(node, ast.AsyncFunctionDef) else ""
                        args = ast.unparse(node.args) if hasattr(ast, 'unparse') else "..."
                        lines.append(f"{prefix}def {node.name}({args}): ...")
                return "\n".join(lines) if lines else "[Empty or No Structural Nodes found]"
            except Exception as e:
                return f"[AST Parsing Failed: {e}]"

        # 🌐 Generic Fallback (AST/Tree-Sitter Upgrade)
        if TREE_SITTER_AVAILABLE:
            lang_map = {
                '.js': 'javascript', '.ts': 'typescript', '.go': 'go', 
                '.rs': 'rust', '.cpp': 'cpp', '.cs': 'c_sharp'
            }
            lang_name = lang_map.get(ext)
            if lang_name:
                try:
                    parser = get_parser(lang_name)
                    tree = parser.parse(bytes(content, "utf8"))
                    query_map = {
                        'javascript': '(function_declaration name: (identifier) @name) (class_declaration name: (identifier) @name)',
                        'typescript': '(function_declaration name: (identifier) @name) (class_declaration name: (identifier) @name) (interface_declaration name: (type_identifier) @name)',
                        'go': '(function_declaration name: (identifier) @name) (type_spec name: (type_identifier) @name)',
                        'rust': '(function_item name: (identifier) @name) (struct_item name: (type_identifier) @name) (trait_item name: (identifier) @name)',
                    }
                    query_str = query_map.get(lang_name)
                    if query_str:
                        language = get_language(lang_name)
                        query = language.query(query_str)
                        captures = query.captures(tree.root_node)
                        extracted_nodes = []
                        for node, tag in captures:
                            line_num = node.start_point[0] + 1
                            line_content = content.splitlines()[line_num - 1].strip() if line_num <= len(content.splitlines()) else node.text.decode('utf8')
                            extracted_nodes.append(f"L{line_num}: {line_content}")
                        if extracted_nodes: return "\n".join(extracted_nodes)
                except: pass

        # Regex Fallback
        patterns = [
            (r'import\s+.*?from\s+[\'"].*?[\'"]', 'import'),
            (r'(?:async\s+)?function\s+(\w+)\s*\((.*?)\)', 'function'),
            (r'class\s+(\w+)\s*(?:extends\s+\w+)?\s*\{', 'class'),
            (r'func\s+(?:\([^\)]+\)\s+)?(\w+)\s*\((.*?)\)', 'go-func'),
            (r'interface\s+(\w+)\s*\{', 'interface'),
            (r'#include\s+.*', 'cpp-include')
        ]
        extracted = []
        lines = content.splitlines()
        for i, line in enumerate(lines):
            stripped = line.strip()
            if not stripped or stripped.startswith(('//', '#', '/*')): continue
            for p, label in patterns:
                if re.search(p, stripped):
                    extracted.append(f"L{i+1}: {stripped}")
                    break
        return "\n".join(extracted) if extracted else "[No Structural Nodes identified]"

    def process_change(self, file_path):
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            content = scrub_secrets(content)
            curr_hash = hashlib.sha256(content.encode()).hexdigest()
            if self.content_cache.get(file_path) == content: return
            
            prev_content = self.content_cache.get(file_path, "")
            diff_lines = []
            for line in difflib.unified_diff(prev_content.splitlines(), content.splitlines(), lineterm=''):
                if (line.startswith('+') or line.startswith('-')) and not (line.startswith('+++') or line.startswith('---')):
                    diff_lines.append(line)
            
            diff_text = "\n".join(diff_lines)
            entropy = SovereignEntropyEngine.calculate(diff_text if prev_content else content, file_path)
            
            # 🛡️ DO NOT update last_hashes here! It must stay pinned to the server head for 409 detection.
            # Only update the memory cache so we don't diff the same change again locally.
            self.content_cache[file_path] = content 
            
            if entropy >= 0.1:
                synapses = self.extract_synapses(content, os.path.basename(file_path))
                symbols = self.extract_symbols(content, file_path)
                print(f"[+] Significant evolution in {os.path.basename(file_path)} (Entropy: {entropy})")
                
                # Attach curr_hash so it can be committed to states.json ONLY on successful push
                self.tx_memory.add(file_path, diff_text, entropy, synapses, symbols, 
                                 full_state=content[:400000000],
                                 metadata_extras={"new_hash": curr_hash})

                # ✅ Optimistic baseline advance: update last_hashes immediately so that
                # rapid sequential saves each use the correct parent_hash.
                # If the push fails (409), push_to_cloud will overwrite with server_hash.
                self.last_hashes[file_path] = curr_hash
            else:
                # Still check if we should update hashes for trivial changes? 
                # Actually, even trivial changes should eventually update the hash if we want to sync them.
                # For now, we only sync significant evolutions.
                pass
        except Exception as e:
            print(f"[!] Engine Error in {os.path.basename(file_path)}: {e}")

    def warm_up(self, root_dir):
        """Silently index all existing files to prevent 'Ghost Changes' on startup."""
        print("[*] Warming up Sentinel Cache...")
        count = 0
        for root, dirs, files in os.walk(root_dir):
            # Prune ignored directories
            dirs[:] = [d for d in dirs if d not in IGNORE_LIST and not d.startswith('.')]
            for file in files:
                if any(x in file for x in IGNORE_LIST) or file.startswith('.'): continue
                path = os.path.join(root, file).replace("\\", "/")
                try:
                    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()
                    curr_hash = hashlib.sha256(content.encode()).hexdigest()
                    self.last_hashes[path] = curr_hash
                    self.content_cache[path] = content
                    count += 1
                except Exception as e:
                    print(f"[*] Warning: Skipping unreadable artifact {path}: {e}")
        print(f"[+] Indexed {count} existing artifacts.")

class DummyMCP:
    def tool(self): return lambda f: f
    def resource(self, path): return lambda f: f
    def prompt(self): return lambda f: f
    def run(self, transport): raise RuntimeError("FastMCP not initialized")

# --- MCP SERVER INITIALIZATION ---
try:
    from mcp.server.fastmcp import FastMCP
    mcp = FastMCP("DalexorGlobal")
except Exception as e:
    sys.stderr.write(f"[Dalexor MCP] FastMCP Init Failed: {e}\n")
    mcp = DummyMCP()

async def cloud_call(endpoint: str, payload: dict, timeout: int = 15):
    """Secure Async Handshake with Cloud Brain."""
    # Hot-reload config to stay in sync with CLI dx init
    cfg = load_global_config()
    api_key = os.getenv("DX_API_KEY") or os.getenv("DALEXORMI_API_KEY") or cfg.get("api_key")
    project_id = os.getenv("DX_PROJECT_ID") or cfg.get("project_id")
    team_secret = os.getenv("DX_TEAM_SECRET") or cfg.get("team_secret")
    cloud_url = os.getenv("DALEXORMI_URL") or cfg.get("cloud_url") or CLOUD_URL

    try:
        async with httpx.AsyncClient(timeout=timeout) as client:
            headers = {"X-DX-Key": api_key}
            payload["project_id"] = project_id
            res = await client.post(f"{cloud_url}/tools/{endpoint}", json=payload, headers=headers)
            if res.status_code == 200:
                data = res.json()
                result = data.get("result", "No result returned.")
                # Auto-Decrypt if E2EE is active
                if isinstance(result, str) and "vault_" in result:
                    result = NeuralVault.unlock(result, DX_TEAM_SECRET)
                return result
            if res.status_code == 429:
                return f"⛔ LIMIT REACHED: Global Brain is resting. Wait an hour or upgrade for higher velocity."
            return f"Brain Error ({res.status_code}): {res.text[:200]}"
    except Exception as e:
        return f"Neural Disconnect: {e}"

@mcp.tool()
async def mcp_health_check() -> str:
    """Call this at the start of every session to verify: auth status, project name, rate limit remaining, index freshness.
    Use when: starting work, diagnosing connectivity issues, or checking if dx sync is needed.
    Returns: cloud status, project name, calls remaining this hour, and when the index was last updated.
    Do NOT use for: understanding code structure (use get_structural_outline instead)."""
    return await cloud_call("mcp_health_check", {}, timeout=15)



@mcp.tool()
async def trace_dependency(symbol: str, depth: int = 1) -> str:
    """Call this BEFORE renaming, moving, or changing a function/class signature.
    Returns: every file + exact line where the symbol is imported or called, with surrounding context.
    Use when: you need to know the blast radius of a change. Returns ALL callsites, not a sample.
    DEPTH: depth=1 (default) returns direct callers. depth=2 returns callers-of-callers — the full transitive blast radius. Max depth=3.
    EXAMPLE: trace_dependency('check_user_rate_limit') → every file that calls it. depth=2 → also every file that calls THOSE files.
    Do NOT use for: finding where a symbol is defined (use find_definition instead).
    Do NOT use for: mapping file-level import relationships (use get_dependency_topology instead)."""
    if not symbol or not symbol.strip():
        return "⛔ Invalid input: symbol name cannot be empty."
    if len(symbol) > 256:
        return "⛔ Invalid input: symbol name too long."
    return await cloud_call("trace_dependency", {"symbol": symbol.strip(), "depth": max(1, min(3, depth))}, timeout=15)






@mcp.tool()
async def find_definition(symbol: str, offset: int = 0, include_callers: bool = False) -> str:
    """Call this to get the ACTUAL CODE of any function, class, or variable — paginated in 80-line pages.
    Use when: you need to read or understand a symbol before editing it or its callers.
    Works for: Python def/class, JS/TS function/const/interface/type, Go func, Rust fn, and all indexed languages.
    PAGINATION: If the result ends with '... N more lines', call again with offset=80 (then 160, 240 etc.) to read the rest.
    CALLERS: Set include_callers=True to get the top 3 call sites alongside the definition — saves a separate trace_dependency call.
    Do NOT use for: finding all USAGES of a symbol (use trace_dependency instead).
    Do NOT use for: understanding a file's overall structure (use get_structural_outline instead)."""
    if not symbol or not symbol.strip():
        return "⛔ Invalid input: symbol name cannot be empty."
    if len(symbol) > 256:
        return "⛔ Invalid input: symbol name too long."
    return await cloud_call("find_definition", {"symbol": symbol.strip(), "offset": offset, "include_callers": include_callers}, timeout=15)


@mcp.tool()
async def get_atomic_diff(file_path: str, include_risk_context: bool = False) -> str:
    """Call this BEFORE editing a file to understand what recently changed and why.
    Use when: you're about to modify a function and need to know if it was recently refactored.
    Returns: unified diff + AI summary of intent + added/removed function signatures + risk level.
    RISK CONTEXT: Set include_risk_context=True to also fetch the last 3 change summaries — turns a single diff into a pattern (e.g. 'changed 3 times in 2 days' raises the interpretation of risk).
    Do NOT use for: understanding current file structure (use get_structural_outline).
    Do NOT use for: viewing the full changelog of a file (use diff_evolution_snapshots)."""
    if not file_path or not file_path.strip():
        return "⛔ Invalid input: file_path cannot be empty."
    if ".." in file_path:
        return "⛔ Invalid input: file path contains illegal traversal sequence."
    return await cloud_call("atomic_diff", {"file_path": file_path.strip(), "include_risk_context": include_risk_context}, timeout=20)

@mcp.tool()
async def get_structural_outline(file_path: str, symbols_only: bool = False, filter_type: str = "all") -> str:
    """Call this FIRST before reading or editing any file. Returns the complete architecture in one call.
    Shows: imports, classes (with methods), MCP tools (with docstrings), functions (with first 10 lines of body).
    Use when: you need to understand a file's structure before diving into specific functions.
    FAST DISCOVERY: Set symbols_only=True for name+line only — no body previews. Much faster on large files.
    FILTERING: Use filter_type='class'|'function'|'mcp_tool'|'import' to narrow results. E.g. filter_type='mcp_tool' instantly lists all exposed MCP tools in a server file.
    Do NOT use for: getting the full body of one specific function (use find_definition).
    Do NOT use for: viewing line-by-line changes in a file (use get_atomic_diff instead).
    If this returns not found, call list_indexed_files first to confirm the correct path."""
    if not file_path or not file_path.strip():
        return "⛔ Invalid input: file_path cannot be empty."
    if ".." in file_path:
        return "⛔ Invalid input: file path contains illegal traversal sequence."
    return await cloud_call("get_structural_outline", {"file_path": file_path.strip(), "symbols_only": symbols_only, "filter_type": filter_type}, timeout=20)


@mcp.tool()
async def get_dependency_topology(file_path: str, detect_circular: bool = True, max_hops: int = 3) -> str:
    """Maps file-level import/call relationships (who imports whom) for a target file.
    Use when: you need to see which files import this file (incoming) and which this file imports (outgoing).
    Use when: assessing architectural coupling or finding circular dependencies.
    EXAMPLE: get_dependency_topology('auth.py') → which files import auth.py. trace_dependency('check_auth') → every line that calls that function.
    detect_circular=True (default) runs DFS to find circular import chains. Set False for faster results on large graphs.
    max_hops controls transitive topology depth (default 3).
    Do NOT use for: finding all call sites of a specific function symbol (use trace_dependency instead).
    Do NOT use for: reading code inside a file (use get_structural_outline or find_definition instead)."""
    if not file_path or not file_path.strip():
        return "⛔ Invalid input: file_path cannot be empty."
    if ".." in file_path:
        return "⛔ Invalid input: file path contains illegal traversal sequence."
    return await cloud_call("dependency_topology", {"file_path": file_path.strip(), "detect_circular": detect_circular, "max_hops": max_hops}, timeout=15)

@mcp.tool()
async def predict_conflicts(file_path: str, suggest_safe_window: bool = False) -> str:
    """Predictive Risk Analysis: Velocity-based edit risk with transitive blast radius and commit intent scoring.
    Returns: 🔴 HIGH / 🟡 MEDIUM / 🟢 STABLE + AI assessment + direct + transitive files at risk.
    SECURITY/CORE edits automatically boost the risk score beyond raw velocity.
    SAFE WINDOW: Set suggest_safe_window=True on HIGH/MEDIUM risk to get an AI recommendation on the safest approach or timing.
    Skip this tool if get_dependency_topology shows 0 incoming connections — isolated files have no blast radius.
    Do NOT use for: understanding what changed (use get_atomic_diff instead)."""
    if not file_path or not file_path.strip():
        return "⛔ Invalid input: file_path cannot be empty."
    if ".." in file_path:
        return "⛔ Invalid input: file path contains illegal traversal sequence."
    return await cloud_call("predict_conflicts", {"file_path": file_path.strip(), "suggest_safe_window": suggest_safe_window}, timeout=35)

@mcp.tool()
async def list_indexed_files(prefix: str = "", sort_by: str = "path", include_last_modified: bool = False) -> str:
    """Returns all file paths currently indexed in the cloud project, grouped by folder.
    Use when: you don't know the exact file path and need to discover what is available.
    Use when: get_structural_outline returns 'Not in cloud index' — use this first to find the correct path.
    SORT: sort_by='recent' shows what's actively being worked on. sort_by='entropy' surfaces the most volatile files first — ideal before a refactor session.
    Optionally pass a prefix (e.g. 'src/', 'api/') to narrow results to a subdirectory.
    Do NOT use for: reading file content (use get_structural_outline or find_definition instead)."""
    return await cloud_call("list_indexed_files", {"prefix": prefix or "", "sort_by": sort_by, "include_last_modified": include_last_modified}, timeout=15)











@mcp.tool()
async def batch_execute(operations: list, project_id: str = None, org_id: str = None) -> str:
    """Execute multiple tool calls in parallel in a single round-trip.
    Use when: you need results from 2+ tools at once (e.g. get_structural_outline + predict_conflicts + get_atomic_diff before a refactor).
    BILLING: Each operation counts as one usage credit — a 5-op batch debits 5 credits, identical to calling each tool individually.
    PARALLEL: All operations run simultaneously — total latency equals the slowest single op, not the sum.
    AUTH: resolve_context runs once and is shared across all operations — saves N-1 auth round-trips.
    RATE LIMIT: All N credits are verified upfront before any ops run. If the batch would exceed your limit, the whole batch is rejected with remaining credits shown.
    EXCLUDED TOOLS: Do NOT include 'mcp_health_check' or 'list_indexed_files' — call those directly as they are session-setup and discovery tools.
    MAX: 10 operations per batch. For larger workloads, split into multiple batches.
    OPERATION FORMAT: Each operation is a dict with 'tool' (required) plus any tool-specific params. E.g.:
      {"tool": "get_structural_outline", "file_path": "server.py"}
      {"tool": "find_definition", "symbol": "batch_execute", "include_callers": true}
      {"tool": "neural_search", "query": "rate limiting logic", "threshold": 0.45}"""
    payload = {"operations": operations}
    if org_id:
        payload["org_id"] = org_id
    return await cloud_call("batch_execute", payload, timeout=60)


# --- CLI COMMANDS ---

def dx_init():
    print("[*] Initializing Dalexor Project Memory...")
    # Stateless Init: Link to cloud project without writing local files
    key_tuple = interactive_verification(force_prompt=True)
    if key_tuple and key_tuple[0]:
        select_project(key_tuple[0], plan=key_tuple[2])
    
    print("[+] Project Initialized. CLI is linked to Cloud Session.")

def dx_sync(args=None, is_seal=False, message=None):
    """Deep Project Analysis: Ingest the current state of all files and map synapses."""
    global DX_API_KEY
    
    import requests
    headers = {"X-DX-Key": DX_API_KEY}
    project_id = None
    
    try:
        user_res = requests.get(f"{CLOUD_URL}/validate", headers=headers, timeout=30)
        if user_res.status_code == 200:
            last_project = user_res.json().get("user", {}).get("last_project_id")
            if last_project:
                project_id = last_project
                os.environ["DX_PROJECT_ID"] = project_id
                fetch_ignore_patterns()
                print(f"[+] Resumed Context: Project {project_id}")

                try:
                    count_res = requests.get(
                        f"{CLOUD_URL}/projects/{project_id}/atom-count",
                        headers=headers, timeout=10
                    )
                    if count_res.status_code == 200:
                        atom_count = count_res.json().get("atom_count", -1)
                        if atom_count == 0:
                            cache_file = os.path.join(os.path.expanduser("~/.dalexor"), "logic_cache.json")
                            states_file = os.path.join(".dalexor", "states.json")
                            for f_tmp in [cache_file, states_file]:
                                if os.path.exists(f_tmp):
                                    try:
                                        os.remove(f_tmp)
                                    except Exception:
                                        pass
                            print("[*] Empty cloud index detected — local cache cleared. Full re-sync starting...")
                except Exception:
                    pass
            else:
                print("[!] No active project. Run 'dx init' first.")
                return
        else:
            print("[!] Authentication failed.")
            return
    except Exception as e:
        print(f"[!] Connection error: {e}")
        return

    if not IGNORE_LIST:
        fetch_ignore_patterns()
        
    print(f"[*] Analyzing files line-by-line for neural mapping into Project: {project_id}...")
    if len(IGNORE_LIST) > len(HARD_IGNORE):
        print(f"🧬 [Security Shield] Active: {', '.join(list(IGNORE_LIST - HARD_IGNORE))}")
    
    PROJECT_CATALOG = set()
    for root, dirs, files in os.walk("."):
        dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d))]
        for f in files:
            full_path = os.path.join(root, f)
            if not is_ignored(full_path):
                PROJECT_CATALOG.add(f)
                if '.' in f:
                    PROJECT_CATALOG.add(f.split('.')[0])
    
    def cloud_callback(payload):
        import requests
        import time
        if DX_SOVEREIGN:
            payload["is_sovereign"] = True
            payload["request_server_encryption"] = True
        retries = 3
        for attempt in range(retries):
            try:
                res = requests.post(f"{CLOUD_URL}/ingest", json=payload, headers={"X-DX-Key": DX_API_KEY, "Content-Type": "application/json"}, timeout=30)
                if res.status_code == 429:
                    print(f"\n[!] Rate Limit Exceeded: Sync paused. Wait an hour or upgrade for higher velocity.")
                    sys.exit(0)
                if res.status_code == 402:
                    print(f"\n[!] Quota Full: Sync aborted. You have reached your atom limit. Manage atoms at dalexor.com")
                    sys.exit(0)
                if res.status_code != 200:
                    print(f"[!] Server returned {res.status_code}: {res.text[:200]}")
                    return None
                return res.json()
            except requests.exceptions.Timeout:
                print(f"[!] Upload timed out after 30s (skipping file, continuing sync...)")
                return None
            except requests.exceptions.RequestException as e:
                if attempt < retries - 1:
                    wait_time = 2 * (attempt + 1)
                    print(f"[!] Connection glitch. Retrying in {wait_time}s...")
                    time.sleep(wait_time)
                else:
                    print(f"[!] Upload failed after {retries} attempts: {str(e)}")
                    return None
            except Exception as e:
                print(f"[!] Critical Error: {str(e)}")
                return None

    def generate_smart_summary(content, filename):
        groq_key = os.getenv("GROQ_API_KEY")
        if groq_key:
            try:
                from groq import Groq
                client = Groq(api_key=groq_key)
                completion = client.chat.completions.create(
                    model="llama-3.1-8b-instant",
                    messages=[
                        {"role": "system", "content": "You are Dalexor, a Sovereign Intelligence Detective. Analyze this evidence. Output a RICH, STRUCTURED summary using these tags: [LOGIC], [SYSTEM], [SECURITY]. Explain exactly WHAT THIS FILE IS FOR. Use sections 'NARRATIVE:' and 'SYMBOLS:'. Keep it precise but visually consistent with the dashboard."},
                        {"role": "user", "content": f"Explain what this file is for and analyze its purpose:\nFilename: {filename}\nContent:\n{content[:2500]}"}
                    ],
                    temperature=0.1
                )
                return completion.choices[0].message.content.strip()
            except Exception as e:
                sys.stderr.write(f"[*] Intelligence Insight restricted: {e}\n")
        ext = os.path.splitext(filename)[1].lower()
        if ext == '.py':
            m = re.search(r'^"""(.*?)"""', content, re.DOTALL)
            if m: return m.group(1).strip().split('\n')[0][:80]
            m = re.search(r"^'''(.*?)'''", content, re.DOTALL)
            if m: return m.group(1).strip().split('\n')[0][:80]
        if ext in ['.js', '.ts', '.jsx', '.tsx']:
            m = re.search(r'/\*\*(.*?)\*/', content, re.DOTALL)
            if m: return re.sub(r'\s*\*\s*', ' ', m.group(1)).strip().split('.')[0][:80]
            m = re.search(r'^//\s*(.*)', content)
            if m: return m.group(1).strip()[:80]
        if ext == '.html':
            m = re.search(r'<title>(.*?)</title>', content, re.IGNORECASE)
            if m: return f"Page: {m.group(1).strip()}"
        if ext == '.md':
            m = re.search(r'^#\s+(.*)', content)
            if m: return f"Doc: {m.group(1).strip()}"
        try:
            if filename == 'requirements.txt':
                ls = [l.strip() for l in content.splitlines() if l.strip() and not l.startswith('#')]
                return f"[SYSTEM] Neural Evidence NARRATIVE: {len(ls)} deps incl {', '.join(ls[:3])}..."
            if filename == 'package.json':
                import json
                try:
                    pkg = json.loads(content)
                    deps = list(pkg.get('dependencies', {}).keys())
                    return f"[SYSTEM] Sovereign Manifest NARRATIVE: Node Config for {pkg.get('name', 'Project')}. Deps: {', '.join(deps[:3])}"
                except: pass
            if ext in ['.bat', '.sh']:
                es = re.findall(r'echo\s+(.*)', content, re.IGNORECASE)
                for e in es:
                    cl = e.strip().strip('"').strip("'")
                    if cl.lower() not in ['off', 'on', '']: return f"[LOGIC] Tactical Script NARRATIVE: {cl}"
        except: pass
        return f"[SYSTEM] Sovereign Sync NARRATIVE: {filename}"

    tx = TransactionMemory(cloud_callback)
    handler = CodeChangeHandler(tx)
    EXTENSION_MAP = {
        '.md': 'document', '.txt': 'document', '.json': 'artifact', '.yaml': 'artifact', '.yml': 'artifact',
        '.py': 'code', '.js': 'code', '.ts': 'code', '.html': 'code', '.css': 'code', '.sh': 'code', 
        '.bat': 'code', '.ps1': 'code'
    }
    count = 0
    synapse_count = 0
    import concurrent.futures

    def process_file(full_path):
        path = os.path.relpath(full_path, ".").replace("\\", "/")
        ext = os.path.splitext(full_path)[1].lower()
        atom_type = EXTENSION_MAP.get(ext, 'code')
        try:
            with open(path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            content = scrub_secrets(content)
            curr_hash = hashlib.sha256(content.encode()).hexdigest()
            force_sync = (args and hasattr(args, 'extra') and args.extra == "force")
            if not force_sync and handler.last_hashes.get(path) == curr_hash: return None
            entropy = SovereignEntropyEngine.calculate(content, path)
            synapses = handler.extract_synapses(content, os.path.basename(path), PROJECT_CATALOG)
            symbols = handler.extract_symbols(content, path)
            smart_summary = generate_smart_summary(content, os.path.basename(path))
            import uuid
            source_id_seed = f"{os.getenv('DX_PROJECT_ID') or 'NONE'}:{path}"
            source_id = str(uuid.uuid5(uuid.NAMESPACE_URL, source_id_seed))
            payload = {
                "content": content[:400000000], "entropy_score": round(entropy, 2), "atom_type": "evolution_event",
                "source": "api", "file_path": path, "file_name": os.path.basename(path), "source_id": source_id,
                "content_hash": curr_hash, "synapses": synapses, "symbols": symbols, "is_sovereign": DX_SOVEREIGN,
                "request_server_encryption": DX_SOVEREIGN, "is_encrypted": False, "project_id": os.getenv("DX_PROJECT_ID"),
                "project_name": os.getenv("DX_PROJECT_NAME") or "Default",
                "metadata": {
                    "file_path": path, "file_name": os.path.basename(path), "summary": smart_summary,
                    "synapses": synapses, "symbols": symbols, "content_hash": curr_hash, "force": force_sync,
                    "sovereign_full_state": content
                }
            }
            return {"payload": payload, "path": path, "atom_type": atom_type, "curr_hash": curr_hash, "content": content, "synapses_count": len(synapses)}
        except Exception as e:
            print(f"[!] Error processing {path}: {e}")
            return None

    files_to_sync = []
    for root, dirs, files_to_walk in os.walk("."):
        dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d))]
        for file in files_to_walk:
            full_path = os.path.join(root, file)
            if not is_ignored(full_path): files_to_sync.append(full_path)

    print(f"[*] Threading Engines Engaged. Processing {len(files_to_sync)} files...")
    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
        futures = {executor.submit(process_file, f): f for f in files_to_sync}
        for future in concurrent.futures.as_completed(futures):
            res_item = future.result()
            if res_item:
                api_res = cloud_callback(res_item["payload"])
                if api_res:
                    handler.last_hashes[res_item["path"]] = res_item["curr_hash"]
                    handler.content_cache[res_item["path"]] = res_item["content"]
                    count += 1
                    synapse_count += res_item["synapses_count"]
                    if api_res.get("status") != "skipped":
                        print(f"[+] Indexed {res_item['atom_type']}: {res_item['path']} ({res_item['synapses_count']} synapses)")
                else: print(f"[!] Upload Failed: {res_item['path']}")
                
    handler.save_hashes()
    if is_seal and count == 0:
        print("\n[!] No modifications detected since last sync. (Nothing to seal)")
    else:
        print(f"\n[+] Sync Complete! {count} artifacts analyzed. {synapse_count} synapses mapped.")

def dx_chat():
    global DX_API_KEY
    print("Ask anything about your project's memory.")
    print("Type 'exit' to quit.")
    print("="*60 + "\n")

    import requests

    def read_local_file(rel_path, cwd, max_chars=5000):
        """Read a file from disk using a relative path, strictly scoped to CWD."""
        # 🛡️ Resolve absolute paths and check bounds to prevent Path Traversal/LFI
        abs_cwd = os.path.abspath(cwd)
        # Normalize slashes cross-platform
        safe_path = os.path.abspath(os.path.join(cwd, *rel_path.split('/')))
        
        # 🚔 Block if the path is OUTSIDE the project directory
        if not safe_path.startswith(abs_cwd):
            logger.warning(f"Sovereign Shield: Blocked attempt to read outside workspace: {rel_path}")
            return None

        try:
            with open(safe_path, "r", encoding="utf-8", errors="ignore") as f:
                return f.read(max_chars)
        except (FileNotFoundError, PermissionError, IsADirectoryError):
            return None

    while True:
        try:
            p = input("[>] You: ").strip()
            if not p:
                continue
            if p.lower() in ['exit', 'quit', 'bye']:
                break

            headers = {"X-DX-Key": DX_API_KEY}
            project_id = os.getenv("DX_PROJECT_ID")
            cwd = os.getcwd()

            # --- PHASE 1: Cloud retrieval — find which files are relevant ---
            try:
                res1 = requests.post(
                    f"{CLOUD_URL}/query",
                    json={"query": p, "project_id": project_id},
                    headers=headers,
                    timeout=60
                )
            except requests.exceptions.Timeout:
                print("[!] Query timed out. Try again.\n")
                continue

            if res1.status_code == 401:
                print("[!] Error (401): Unauthorized. Your API Key might be invalid.\n")
                DX_API_KEY, _, _ = interactive_verification()
                continue
            if res1.status_code != 200:
                print(f"[!] Brain Error ({res1.status_code}): {res1.text}\n")
                continue

            data1 = res1.json()
            intel = data1.get("intelligence", {})
            signals = intel.get("signals", [])
            neighbors = intel.get("synaptic_neighborhood", [])
            all_sources = signals + neighbors

            # --- PHASE 2: Read actual local file content for top signals ---
            file_contexts = []
            for src in all_sources[:6]:  # Top 6 sources to stay within cloud token limits
                path = src.get("path", "")
                if not path:
                    continue
                raw = read_local_file(path, cwd)
                if raw:
                    file_contexts.append(f"=== FILE: {path} ===\n{raw}")

            if file_contexts:
                # Re-query cloud with actual file content embedded.
                # The cloud LLM answers from this real content — no hallucination possible.
                embedded_content = "\n\n".join(file_contexts)
                grounded_query = (
                    f"[ACTUAL FILE CONTENTS FROM PROJECT — USE ONLY THIS, DO NOT GUESS]\n"
                    f"{embedded_content}\n\n"
                    f"[QUESTION] {p}\n\n"
                    f"Answer strictly and only from the file contents above. "
                    f"If the answer is not present, say so clearly."
                )
                try:
                    res2 = requests.post(
                        f"{CLOUD_URL}/query",
                        json={"query": grounded_query, "project_id": project_id},
                        headers=headers,
                        timeout=60
                    )
                    if res2.status_code == 200:
                        answer = res2.json().get("context", "")
                        if answer:
                            print(f"[*] Brain: {answer}\n")
                        else:
                            print(f"[*] Brain: {data1.get('context', '...')}\n")
                    else:
                        # Phase 2 failed — fall back to phase 1 answer
                        print(f"[*] Brain: {data1.get('context', '...')}\n")
                except requests.exceptions.Timeout:
                    print(f"[*] Brain: {data1.get('context', '...')}\n")
            else:
                # No local files found — show original cloud answer
                print(f"[*] Brain: {data1.get('context', '...')}\n")

            # Show sources (file paths only, no verbose summaries)
            if all_sources:
                print("-" * 60)
                print(f"[*] Sources ({len(signals)} signals, {len(neighbors)} neural neighbors):")
                for s in signals:
                    print(f"  [SIGNAL] {s['path']}")
                for n in neighbors:
                    print(f"  [NEURAL] {n['path']}")
                print("-" * 60 + "\n")

        except KeyboardInterrupt:
            break
        except Exception as e:
            print(f"[!] Connection Error: {e}\n")
            break


def dx_restore(intent: str):
    """Intent-Based Restore: Search for a conceptual state and apply the code locally."""
    print(f"[*] Navigating through Neural History for intent: '{intent}'...")
    
    # We call the MCP bridge tool mcp_restore_by_intent
    import asyncio
    res_json = asyncio.run(cloud_call("restore_by_intent", {"intent": intent}))
    
    try:
        import json
        res = json.loads(res_json)
        if not res.get("success"):
            print(f"[!] Restore Failed: {res.get('error')}")
            return

        file_path = res.get("file_path")
        content = res.get("content")
        summary = res.get("match_summary")

        if not file_path or not content:
            print("[!] Evolution state found, but data is incomplete.")
            return

        print(f"[+] Match Found: {summary}")
        
        # --- SMART PATH RESOLUTION ---
        target_file = file_path
        if not os.path.exists(target_file):
            base_name = os.path.basename(file_path)
            # Search for matching filename in current directory subtree
            print(f"[*] Original path '{file_path}' not found. Searching workspace...")
            found_paths = []
            for root, dirs, files in os.walk("."):
                # Filter out ignore list
                dirs[:] = [d for d in dirs if d not in [".git", "__pycache__", "venv", "node_modules"]]
                if base_name in files:
                    found_paths.append(os.path.join(root, base_name))
            
            if found_paths:
                if len(found_paths) == 1:
                    print(f"[*] Found matching local file: {found_paths[0]}")
                    target_file = found_paths[0]
                else:
                    print(f"[*] Multiple candidates found for '{base_name}':")
                    for i, p in enumerate(found_paths):
                        print(f"  {i+1}. {p}")
                    choice = input(f"[?] Select target file index (1-{len(found_paths)}) or 'n' to use original: ").strip()
                    if choice.isdigit() and 1 <= int(choice) <= len(found_paths):
                        target_file = found_paths[int(choice)-1]
        
        # --- CONTENT PREVIEW ---
        lines = content.splitlines()
        preview_len = min(len(lines), 5)
        print("\n" + "-"*40)
        print(f" CONTENT PREVIEW ({target_file}):")
        print("-"*40)
        for i in range(preview_len):
            print(f" {i+1} | {lines[i]}")
        if len(lines) > preview_len:
            print(f" ... ({len(lines) - preview_len} more lines)")
        print("-"*40 + "\n")

        if any(line.startswith(('+', '-')) for line in lines[:20]) and len(lines) < 50:
             print("⚠️  WARNING: The detected content looks like a DIFF or a SNIPPET rather than a full file.")

        confirm = input(f"[?] Apply restoration to '{target_file}'? (y/N): ").lower()
        if confirm == 'y':
            try:
                # Ensure directory exists
                os.makedirs(os.path.dirname(os.path.abspath(target_file)), exist_ok=True)
                with open(target_file, "w", encoding="utf-8") as f:
                    f.write(content)
                print(f"[+] Time Travel Successful: {target_file} has been restored to its conceptual state.")
            except Exception as e:
                print(f"[!] Disk Write Error: {e}")
        else:
            print("[*] Restoration aborted.")

    except Exception as e:
        print(f"[!] Neural Parsing Error: {e}")
        if 'res_json' in locals():
            print(f"[*] Raw Result: {res_json[:500]}...")

def run_agent():
    global DX_API_KEY
    import sys
    # Universal Case-Insensitivity fix
    if len(sys.argv) > 1:
        valid_cmds = ["init", "watch", "chat", "sync", "restore", "mcp", "tail", "review"]
        if sys.argv[1].lower() in valid_cmds:
            sys.argv[1] = sys.argv[1].lower()

    parser = argparse.ArgumentParser(description="Dalexor MI Sovereign CLI")
    parser.add_argument("command", nargs="?", default="init")
    parser.add_argument("extra", nargs="?") # For file paths or messages
    args = parser.parse_args()

    # Manual Choice Validation (Advertised: init, watch, chat, sync, restore, review)
    advertised = ["init", "watch", "chat", "sync", "restore", "review"]
    hidden = ["mcp", "tail"]
    if args.command not in advertised + hidden:
        parser.error(f"argument command: invalid choice: '{args.command}' (choose from {', '.join([repr(c) for c in advertised])})")


    # Step 1: Handle Initialization separately (forces prompt)
    if args.command == "init": 
        dx_init()
        return

    # Step 2: Ensure Auth & Get Plan (for non-init commands)
    if args.command == "mcp":
        # Non-blocking auth check for headless MCP
        cfg = load_global_config()
        DX_API_KEY = os.getenv("DX_API_KEY") or os.getenv("DALEXORMI_API_KEY") or cfg.get("api_key")
        key_tuple = (DX_API_KEY, "Sovereign Agent", "Headless")
    else:
        # Normal Auth for interactive commands
        key_tuple = interactive_verification()
        if not key_tuple or not key_tuple[0]:
            print("[!] Error: Valid API Key required.")
            sys.exit(1)
        DX_API_KEY = key_tuple[0]
        # Only print banner for interactive commands (not MCP)
        if args.command != "mcp":
            print_banner(key_tuple[1], key_tuple[2])
    
    # NEW: Load Dynamic Guardrails from the Global Brain
    fetch_ignore_patterns(quiet=(args.command == "mcp"))

    # Team Tail Handler (SSE Stream)
    if args.command == "tail":
        print(f"[*] Navigating through Neural Streams for Project Universe...")
        import requests, json
        my_email = key_tuple[1]
        try:
            res = requests.get(f"{CLOUD_URL}/tail", headers={"X-DX-Key": DX_API_KEY}, stream=True, timeout=None)
            if res.status_code == 200:
                print("[+] Connection Established. Monitoring project collisions...")
                for line in res.iter_lines():
                    if not line: continue
                    decoded = line.decode('utf-8')
                    if decoded.startswith('data: '):
                        event = json.loads(decoded[6:])
                        if event.get('type') == 'presence':
                            e_data = event.get('data', {})
                            if e_data.get('user') != my_email:
                                print(f" \033[93m⚡ [TEAM ACTIVITY]\033[0m {e_data.get('user')} is touching \033[1m{e_data.get('file')}\033[0m")
            else:
                print(f"[!] Stream Error: {res.status_code} - {res.text}")
        except KeyboardInterrupt:
            print("\n[*] Monitoring Stopped.")
        except Exception as e:
            print(f"[!] Transmission Error: {e}")
        return

    if args.command == "chat": dx_chat(); return
    if args.command == "restore":
        if not args.extra:
            print("[!] Error: You must provide an intent description. (e.g. dx restore 'error handling')")
            return
        dx_restore(args.extra); return

    if args.command == "sync": dx_sync(args); return

    if args.command == "review":
        # Skip localhost bridge check - go directly to cloud-based popup
        # The popup works entirely with cloud API, no local server needed
        import requests
        print("\n[*] Neural Pulse: Prepping Sovereign Review Layer...")
        
        # Fetch pending reviews directly from cloud
        pending = _cloud_get_pending()
        
        if not pending:
            print("[✅] No pending teammate updates. Your workspace is in sync.")
            return
        
        print(f"[*] Found {len(pending)} pending updates. Launching Review Popup...")
        dx_review_popup(pending)
        return
    if args.command == "watch":
        # Watchdog & Sentinel initialization (Interactive Mode Only)
        def push_to_cloud(payload):
            import requests
            try:
                # Ensure payload is a dictionary
                if not isinstance(payload, dict):
                    logger.warning(f"Invalid payload type: {type(payload)}")
                    return

                metadata = payload.get('metadata', {})
                if not isinstance(metadata, dict): metadata = {}
                summary = metadata.get('summary', 'Evolution')
                
                # Inject Project Metadata
                project_id = os.getenv("DX_PROJECT_ID")
                if not project_id:
                    print(f"[!] Critical Alert: Intelligence push aborted. No project context active. Run 'dx init'.")
                    return

                payload["project_id"] = project_id
                payload["project_name"] = os.getenv("DX_PROJECT_NAME") or "Unnamed Project"
                payload["is_sovereign"] = DX_SOVEREIGN
                
                # 🔐 Preserve is_encrypted flag if already set (e.g., by dx seal)
                if "is_encrypted" not in payload:
                    payload["is_encrypted"] = False

                # 🛡️ CONFLICT GUARD: Attach parent_hash so server can detect concurrent edits.
                file_path = metadata.get("file_path", "")
                if file_path:
                    prior_hash = handler.last_hashes.get(file_path)
                    if prior_hash:
                        if "metadata" not in payload or not isinstance(payload["metadata"], dict):
                            payload["metadata"] = metadata
                        payload["metadata"]["parent_hash"] = prior_hash
                
                print(f"[*] Uploading '{summary}' to Project '{payload.get('project_name', 'Default')}'...")
                res = requests.post(f"{CLOUD_URL}/ingest", json=payload, headers={"X-DX-Key": DX_API_KEY}, timeout=60)
                
                if res.status_code == 200:
                    try:
                        data = res.json()
                        insight = data.get('ai_insight', 'Indexed') if isinstance(data, dict) else 'Indexed'
                        print(f"[+] Synced: {insight}")
                        
                        # 🔗 SUCCESS: Now we can safely update the local baseline hash
                        new_hash = payload.get("new_hash")
                        if new_hash and file_path:
                            handler.last_hashes[file_path] = new_hash
                            handler.save_hashes()
                    except:
                        print(f"[+] Synced: Indexed")
                elif res.status_code == 409:
                    print(f"\n\033[93m⚠️  CONFLICT DETECTED\033[0m: {os.path.basename(file_path)}")
                    print(f"   A teammate pushed a newer version. Your changes were held.")
                    print(f"   \033[1m[Press R]\033[0m to review inline, or run \033[1mdx review\033[0m in a new terminal.\n")

                    # ✅ CRITICAL FIX: advance the local hash baseline to the server's version.
                    try:
                        conflict_data = res.json()
                        server_hash = conflict_data.get("server_hash")
                        if server_hash and file_path:
                            handler.last_hashes[file_path] = server_hash
                            handler.save_hashes()
                            print(f"   [*] Local baseline advanced. Your next save will create a new version.")
                    except Exception as hash_err:
                        print(f"   [!] Warning: Could not advance hash baseline: {hash_err}")

                    # Auto-launch review popup so user doesn't have to press R manually
                    def _auto_review():
                        time.sleep(1.0)  # brief pause so conflict message is visible first
                        try:
                            pending = _cloud_get_pending()
                            if pending:
                                print(f"\n[*] Found {len(pending)} pending update(s). Launching Review Popup...")
                                import __main__
                                q = getattr(__main__, "_popup_queue", None)
                                if q: q.put(pending)
                                else: dx_review_popup(pending)
                        except Exception as re:
                            logger.debug(f"Auto-review launch failed: {re}")
                    threading.Thread(target=_auto_review, daemon=True).start()
                elif res.status_code == 429:
                    print(f"[!] Rate Limit Exceeded: Your plan is currently throttled.")
                elif res.status_code == 402:
                    print(f"[!] Quota Full: You have reached your Intelligence Atom limit.")
                else:
                    print(f"[!] Handshake Refused: {res.status_code} - {res.text[:200]}")
            except Exception as e: 
                import traceback
                traceback.print_exc()
                print(f"[!] Transmission Error: {e}")

        tx = TransactionMemory(push_to_cloud)
        handler = CodeChangeHandler(tx)
        handler.warm_up(".")
        # ✅ FIX: expose handler at module level so MCP tools (reject/accept)
        # can update the live hash baseline without creating ghost instances
        import __main__
        __main__._live_handler = handler
        observer = Observer()
        observer.schedule(handler, ".", recursive=True)
        observer.start()

        # ─────────────────────────────────────────────────────────
        #  REAL-TIME REMOTE UPDATE LISTENER
        #  Connects to the SSE /tail stream and queues incoming
        #  teammate file changes into PENDING_REMOTE for safe review.
        # ─────────────────────────────────────────────────────────
        def remote_update_listener():
            import requests as _req, json as _json
            backoff = 5  # seconds
            while True:
                try:
                    res = _req.get(f"{CLOUD_URL}/tail", headers={"X-DX-Key": DX_API_KEY}, stream=True, timeout=None)
                    if res.status_code != 200:
                        time.sleep(backoff)
                        backoff = min(backoff * 2, 120)
                        continue
                    backoff = 5
                    logger.debug("Remote update listener connected.")
                    for line in res.iter_lines():
                        if not line: continue
                        decoded = line.decode("utf-8")
                        if not decoded.startswith("data: "): continue
                        try:
                            event = _json.loads(decoded[6:])
                        except: continue
                        if event.get("type") != "evolution": continue
                        e_data = event.get("data", {})
                        if e_data.get("user") == key_tuple[1]: continue
                        if e_data.get("project_id") != os.getenv("DX_PROJECT_ID"): continue
                        
                        file_path = e_data.get("file_path", "")
                        updated_by = e_data.get("user", "teammate")
                        
                        print(f"\n\033[93m📥 [INCOMING CHANGE]\033[0m {updated_by} updated \033[1m{os.path.basename(file_path)}\033[0m")
                        print(f"   Run \033[1mdx review\033[0m to preview and accept.")

                except Exception as e:
                    logger.debug(f"Remote listener error: {e}")
                    time.sleep(5)

        threading.Thread(target=remote_update_listener, daemon=True).start()

        print(f"[*] Telescope Sentinel Active in {os.getcwd()}. Press Ctrl+C to stop.")
        print("\n" + "-"*60)
        print(" MANUAL TRIGGER: THE 'S' KEY")
        print("-"*60)
        print(" While dx watch is running, you have a 'tactical trigger':")
        print("\n [Press S] (or k): Seal milestone — prompts for a commit")
        print("   message, then immediately flushes all pending changes.")
        print("\n [Press R]: Open the Neural Review popup inline without")
        print("   stopping dx watch. Review conflicts while watching.")
        print("\n [Bypass Coalescence]: A manual seal bypasses the 20-minute")
        print(" window, forcing the creation of a fresh, stable version.")
        print("-"*60 + "\n")
        
        # Keyboard Listener for sealing milestones
        def kbd_loop():
            try:
                if os.name == 'nt':
                    import msvcrt
                    while True:
                        if msvcrt.kbhit():
                            ch = msvcrt.getch().decode('utf-8', errors='ignore').lower()
                            if ch in ['s', 'k', '\x10']:
                                # Prompt for commit message
                                sys.stdout.write("\n[💬] Seal message (optional, Enter to skip): ")
                                sys.stdout.flush()
                                msg = ""
                                while True:
                                    c = msvcrt.getwche()
                                    if c in ('\r', '\n'):
                                        sys.stdout.write("\n")
                                        break
                                    elif c == '\x08':  # backspace
                                        if msg:
                                            msg = msg[:-1]
                                            sys.stdout.write('\b \b')
                                            sys.stdout.flush()
                                    else:
                                        msg += c
                                tx.flush_all(commit_message=msg.strip() or None)
                            elif ch == 'r':
                                # Launch review popup without stopping dx watch
                                def _open_review():
                                    try:
                                        pending = _cloud_get_pending()
                                        if not pending:
                                            print("\n[✅] No pending teammate updates. Your workspace is in sync.")
                                        else:
                                            print(f"\n[*] Found {len(pending)} pending updates. Launching Review Popup...")
                                            import __main__
                                            q = getattr(__main__, "_popup_queue", None)
                                            if q: q.put(pending)
                                            else: dx_review_popup(pending)
                                    except Exception as re:
                                        print(f"\n[!] Review error: {re}")
                                threading.Thread(target=_open_review, daemon=True).start()
                        time.sleep(0.1)
                else:
                    import select, termios, tty
                    def getch():
                        fd = sys.stdin.fileno()
                        old_settings = termios.tcgetattr(fd)
                        try:
                            tty.setraw(sys.stdin.fileno())
                            ch = sys.stdin.read(1)
                        finally:
                            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
                        return ch
                    while True:
                        if select.select([sys.stdin], [], [], 0.1)[0]:
                            ch = getch().lower()
                            if ch in ['s', 'k', '\x10']:
                                sys.stdout.write("\n[💬] Seal message (optional, Enter to skip): ")
                                sys.stdout.flush()
                                termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN,
                                                  termios.tcgetattr(sys.stdin.fileno()))
                                msg = input().strip()
                                tx.flush_all(commit_message=msg or None)
                            elif ch == 'r':
                                def _open_review():
                                    try:
                                        pending = _cloud_get_pending()
                                        if not pending:
                                            print("\n[✅] No pending teammate updates.")
                                        else:
                                            print(f"\n[*] Opening review for {len(pending)} file(s)...")
                                            import __main__
                                            q = getattr(__main__, "_popup_queue", None)
                                            if q: q.put(pending)
                                            else: dx_review_popup(pending)
                                    except Exception as re:
                                        print(f"\n[!] Review error: {re}")
                                threading.Thread(target=_open_review, daemon=True).start()
                        time.sleep(0.1)
            except Exception as e:
                print(f"[!] Keyboard Sentinel Activation Error: {e}")

        if sys.stdin.isatty():
            threading.Thread(target=kbd_loop, daemon=True).start()

        # Team Presence Monitor (Real-time Collision Awareness)
        def team_monitor():
            import requests, json
            my_email = key_tuple[1]
            project_id = os.getenv("DX_PROJECT_ID")
            try:
                # Use stream=True to keep connection open (SSE)
                res = requests.get(f"{CLOUD_URL}/tail", headers={"X-DX-Key": DX_API_KEY}, stream=True, timeout=None)
                if res.status_code == 200:
                    for line in res.iter_lines():
                        if not line: continue
                        decoded = line.decode('utf-8')
                        if decoded.startswith('data: '):
                            event = json.loads(decoded[6:])
                            if event.get('type') == 'presence':
                                e_data = event.get('data', {})
                                # Filter for same project, different user
                                if e_data.get('project_id') == project_id and e_data.get('user') != my_email:
                                    print(f" \033[93m⚡ [TEAM ACTIVITY]\033[0m {e_data.get('user')} is touching \033[1m{e_data.get('file')}\033[0m")
            except: pass

        threading.Thread(target=team_monitor, daemon=True).start()

        # Main thread owns Tkinter — popup requests must run here,
        # not in background threads, to avoid Tcl_AsyncDelete crashes.
        import queue as _queue
        _popup_queue = _queue.Queue()
        import __main__
        __main__._popup_queue = _popup_queue

        try:
            while True:
                try:
                    pending_data = _popup_queue.get_nowait()
                    try:
                        dx_review_popup(pending_data)
                    except Exception as _pe:
                        logger.debug(f"Popup error: {_pe}")
                except _queue.Empty:
                    pass
                time.sleep(0.1)
        except KeyboardInterrupt:
            print("[*] Watcher Stopped by User.")
        finally:
            observer.stop()
            observer.join()

    if args.command == "mcp":
        # Start MCP server - this will block until the process is killed
        # transport='stdio' is used for IDE integrations (Cursor, etc.)
        mcp.run(transport='stdio')
        # Ensure we cleanup observer if mcp.run ever returns
        observer.stop()
        observer.join()


def extract_outline(content: str, file_path: str) -> str:
    """Extracts a skeletal outline of a file (Imports, Classes, Functions, Decorators) to save tokens."""
    import ast, os, re
    ext = os.path.splitext(file_path)[1].lower()
    
    # 🐍 Python (AST Based - Precise)
    if ext == '.py':
        try:
            tree = ast.parse(content)
            lines = []
            for node in ast.iter_child_nodes(tree):
                # Imports
                if isinstance(node, (ast.Import, ast.ImportFrom)):
                    try: lines.append(f"[{node.lineno}] {ast.unparse(node)}")
                    except: pass
                # Classes
                elif isinstance(node, ast.ClassDef):
                    # Class Definition
                    bases = f"({', '.join([ast.unparse(b) for b in node.bases])})" if node.bases else ""
                    lines.append(f"[{node.lineno}] class {node.name}{bases}:")
                    # Scan class body for methods
                    for sub in node.body:
                        if isinstance(sub, (ast.FunctionDef, ast.AsyncFunctionDef)):
                            prefix = "async " if isinstance(sub, ast.AsyncFunctionDef) else ""
                            # Extract arguments safely
                            args = ast.unparse(sub.args) if hasattr(ast, 'unparse') else "..."
                            lines.append(f"    [{sub.lineno}] {prefix}def {sub.name}({args}): ...")
                # Top-level Functions
                elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                    prefix = "async " if isinstance(node, ast.AsyncFunctionDef) else ""
                    args = ast.unparse(node.args) if hasattr(ast, 'unparse') else "..."
                    lines.append(f"[{node.lineno}] {prefix}def {node.name}({args}): ...")
            return "\n".join(lines) if lines else "[Empty or No Structural Nodes found]"
        except Exception as e:
            return f"[AST Parsing Failed: {e}]"

    # 🌐 Generic Fallback (Regex based for JS, TS, Go, etc.)
    patterns = [
        (r'import\s+.*', 'import'),
        (r'export\s+(?:async\s+)?(?:function|class|const|let|var)\s+\w+', 'export'),
        (r'(?:async\s+)?function\s+(\w+)\s*\(', 'function'),
        (r'class\s+(\w+)\s*', 'class'),
        (r'type\s+(\w+)\s*=', 'type'),
        (r'interface\s+(\w+)\s*', 'interface')
    ]
    
    extracted = []
    lines = content.splitlines()
    for i, line in enumerate(lines):
        stripped = line.strip()
        if not stripped or stripped.startswith(('//', '#', '/*')): continue
        for p, label in patterns:
            if re.search(p, stripped):
                extracted.append(f"[{i+1}] {stripped}")
                break
    
    return "\n".join(extracted) if extracted else "[No distinct structural patterns detected]"


@mcp.tool()
async def diff_evolution_snapshots(file_path: str, limit: int = 7, since_days: int = 30, symbol: str = ""):
    """Call this to understand WHY a file looks the way it does — shows the history of conceptual changes.
    Use when: you encounter code that seems arbitrary or over-engineered and want to know the reasoning.
    Returns: timestamped changelog with categories (Innovation/Correction/Architecture Shift) and entropy scores.
    TIME RANGE: Use since_days to limit history (default 30 days). Set since_days=0 for all history.
    SYMBOL FILTER: Use symbol='function_name' to show only snapshots where that specific function changed.
    Do NOT use for: seeing exact line-by-line diff (use get_atomic_diff for that)."""
    return await cloud_call("diff_evolution_snapshots", {"file_path": file_path, "limit": limit, "since_days": since_days, "symbol": symbol or None})


@mcp.tool()
async def neural_search(query: str, threshold: float = 0.45, limit: int = 5, file_filter: str = "", type_filter: str = ""):
    """Call this to find code by MEANING, not by exact text match. Uses vector embeddings.
    Only call this if find_definition returned NOT FOUND — for known symbol names, find_definition is faster and more precise.
    Use when: you know WHAT you want but not WHERE it is (e.g., 'rate limiting logic', 'auth middleware').
    THRESHOLD: Start at 0.45 (default). If empty, retry at 0.25. Never exceed 0.65 for broad queries.
    FILE FILTER: Use file_filter='src/auth/' to restrict search to a subdirectory and eliminate noise.
    TYPE FILTER: Use type_filter='function'|'class'|'file' to restrict result types.
    Do NOT use for: finding a specific function by name (use find_definition instead)."""
    res = await cloud_call("neural_search", {"query": query, "threshold": threshold, "limit": limit, "file_filter": file_filter or None, "type_filter": type_filter or None})
    try:
        data = json.loads(res) if isinstance(res, str) else res
        if not isinstance(data, list): return str(res)
        
        # Keep client dedup for safety
        seen_contents = set()
        unique_data = []
        for m in data:
            sig = hashlib.md5(f"{m.get('path')}{m.get('summary')}".encode()).hexdigest()
            if sig not in seen_contents:
                seen_contents.add(sig)
                unique_data.append(m)
        data = unique_data[:limit]

        lang_map = {'py': 'python', 'js': 'javascript', 'ts': 'typescript', 'html': 'html', 'css': 'css', 'go': 'go', 'md': 'markdown', 'json': 'json'}
        matches_md = ""
        
        for i, m in enumerate(data):
            path = m.get("path", "unknown")
            relevance = m.get("relevance", 0)
            reasoning = m.get("reasoning", "Semantic similarity")
            snippet = m.get("context_snippet", "")
            server_line_start = m.get("line_start")
            server_line_end   = m.get("line_end")

            if isinstance(snippet, str) and snippet.startswith("vault_") and DX_TEAM_SECRET:
                snippet = NeuralVault.unlock(snippet, DX_TEAM_SECRET)

            # 🔬 SOVEREIGN PINPOINTING
            # Fast-path: server already pinpointed and sent line numbers — use them directly.
            # Slow-path: server sent a large snippet but no line numbers — scan locally.
            line_info = ""
            pinpointed = False
            preview = ""
            ext = path.rsplit(".", 1)[-1] if "." in path else "text"

            try:
                if (
                    snippet
                    and not str(snippet).startswith("vault_")
                    and not snippet.startswith("- *")
                    and server_line_start is not None
                ):
                    # ✅ SERVER-SIDE PINPOINT: trust the line numbers server computed
                    sl = server_line_start
                    el = server_line_end or (sl + snippet.count('\n'))
                    line_info = f" (Lines {sl}-{el})"
                    numbered = [f"{sl + j}: {ln}" for j, ln in enumerate(snippet.splitlines())]
                    preview = "\n".join(numbered)
                    pinpointed = True

                elif snippet and not str(snippet).startswith("vault_") and not snippet.startswith("- *"):
                    # 🔍 LOCAL SCAN FALLBACK: server sent content but no line numbers
                    fc = snippet
                    fc_low = fc.lower()

                    _filler = {"the","and","file","logic","detail","implementation","code","example",
                               "module","in","for","of","with","from","is","to","a","an","by","its"}
                    _path_ext = re.compile(r'^[\w\-]+\.(py|js|ts|tsx|jsx|go|rs|rb|java|cpp|cs|php|md|txt|json|yaml|sh|sql)$')
                    q_primary_loc = []
                    q_secondary_loc = []
                    for w in query.split():
                        t = re.sub(r'[^a-zA-Z0-9_.]', '', w.lower())
                        if len(t) <= 2: continue
                        if _path_ext.match(t): continue
                        if t in _filler: continue
                        # Expand camelCase → snake so "checkUserRateLimit" finds "check_user_rate_limit"
                        snake = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', t).lower()
                        for tok in {t, snake}:
                            if '_' in tok or len(tok) >= 10:
                                q_primary_loc.append(tok)
                            else:
                                q_secondary_loc.append(tok)
                    q_primary_loc   = list(dict.fromkeys(q_primary_loc))
                    q_secondary_loc = list(dict.fromkeys(q_secondary_loc))
                    search_terms = q_primary_loc + q_secondary_loc

                    best_idx = -1
                    for term in search_terms:
                        for pat in [
                            f"async def {term}",
                            f"def {term}",
                            f"class {term}",
                            f"function {term}",
                            f"const {term}",
                        ]:
                            idx = fc_low.find(pat)
                            if idx != -1:
                                best_idx = idx
                                break
                        if best_idx != -1:
                            break

                    if best_idx == -1:
                        for term in search_terms:
                            if len(term) > 5:
                                idx = fc_low.find(term)
                                if idx != -1:
                                    best_idx = max(0, idx - 50)
                                    break

                    if best_idx != -1:
                        block_end = best_idx + 3000
                        next_def = re.search(r'\n(?:async def |def |class )[a-zA-Z]', fc[best_idx + 10:])
                        if next_def:
                            block_end = min(block_end, best_idx + 10 + next_def.start() + 1)

                        local_snippet = ("..." if best_idx > 0 else "") + fc[best_idx:block_end]
                        sl = fc.count('\n', 0, best_idx) + 1
                        el = sl + local_snippet.count('\n')
                        line_info = f" (Lines {sl}-{el})"
                        numbered = [f"{sl + j}: {ln}" for j, ln in enumerate(local_snippet.splitlines())]
                        preview = "\n".join(numbered)
                        pinpointed = True
            except:
                pass

            summary_text = m.get('summary', '') or f"Semantically related file: {path}"
            if pinpointed:
                matches_md += f"#### {i+1}. `{path}`{line_info} ({relevance}% Relevance)\n- **Reason:** {reasoning}\n- **Summary:** {summary_text}\n```{lang_map.get(ext, ext)}\n{preview}\n```\n"
            else:
                matches_md += f"#### {i+1}. `{path}` ({relevance}% Relevance)\n- **Reason:** {reasoning}\n- **Summary:** {summary_text}\n- *No pinpointed definition — run `find_definition <n>` if you need the code body.*\n"

        
        # 🚀 ARCHITECTURAL DIVERGENCE & NARRATIVE SYNTHESIS
        synthesis_md = ""


        if not any(m.get("relevance",0) > 70 for m in data):
            quality_note = "\n> ⚠️ Low confidence results. Try a more specific query or run `dx sync` to refresh the index."
        else:
            quality_note = ""
        return f"### 🧠 Neural Search: `{query}`\n**Matches:** {len(data)} | Re-ranked\n{synthesis_md}\n{matches_md}{quality_note}\n> 💡 Run `find_definition <name>` to get the actual code body of any match."
    except Exception as e:
        return f"### 🧠 Neural Search: `{query}`\n---\n**Error During Synthesis:** {e}"

@mcp.tool()
async def trace_logic_heritage(file_path: str, max_depth: int = 10, stop_at: str = ""):
    """Call this when code decisions seem arbitrary or confusing — it shows the full history of WHY.
    Use when: you see a pattern that looks wrong and want to know if there's a reason before changing it.
    Returns: parent-chain from oldest to newest, tagged [LOGIC]/[SECURITY]/[REFACTOR]/[SYSTEM].
    DEPTH: Use max_depth to limit traversal (default 10). Set lower (3-5) for recent history only.
    STOP AT: Use stop_at='SECURITY' to walk back only until the last security decision. Options: SECURITY, ARCHITECTURE, REFACTOR, SYSTEM, CORRECTION.
    Do NOT use for: seeing recent changes only (use get_atomic_diff or diff_evolution_snapshots)."""
    # stop_at can be a comma-separated string — split into list for the cloud
    stop_at_list = [t.strip().upper() for t in stop_at.split(",") if t.strip()] if stop_at else None
    return await cloud_call("trace_logic_heritage", {"file_path": file_path, "max_depth": max_depth, "stop_at": stop_at_list})



def _cloud_get_pending():
    """Fetches the current user's personal review queue from the cloud."""
    import requests
    pid = os.getenv("DX_PROJECT_ID")
    try:
        url = f"{CLOUD_URL}/review/pending"
        params = {"project_id": pid} if pid else {}
        resp = requests.get(url, headers={"X-DX-Key": DX_API_KEY}, params=params, timeout=10)
        if resp.status_code == 200:
            data = resp.json()
            # Handle both 'pending' dict and a flat list of items
            return data.get("pending") or data.get("items") or data or {}
    except Exception as e:
        logger.debug(f"Pending fetch failed: {e}")
    return {}

async def preview_incoming_change(file_path: str):
    return await cloud_call("preview_incoming_change", {"file_path": file_path})

async def accept_incoming_change(file_path: str, review_id: str = None):
    """Call the /review/accept endpoint directly."""
    import httpx
    project_id = os.getenv("DX_PROJECT_ID")
    try:
        async with httpx.AsyncClient() as client:
            payload = {}
            if review_id:
                payload["review_id"] = review_id
            elif file_path:
                payload["file_path"] = file_path
            else:
                return "Error: Either review_id or file_path required"
            
            if project_id:
                payload["project_id"] = project_id
            
            resp = await client.post(
                f"{CLOUD_URL}/review/accept",
                json=payload,
                headers={"X-DX-Key": DX_API_KEY},
                timeout=15.0
            )
            if resp.status_code == 200:
                data = resp.json()
                # If accepted, fetch the actual file content from snapshot
                if data.get("ok"):
                    try:
                        snap_params = {"file_path": file_path}
                        if project_id:
                            snap_params["project_id"] = project_id
                        # Pass the specific atom_id so we get the teammate's version,
                        # not our own latest push which would also be the newest atom
                        accepted_atom_id = data.get("atom_id")
                        if accepted_atom_id:
                            snap_params["atom_id"] = accepted_atom_id
                        snap = await client.get(
                            f"{CLOUD_URL}/file-snapshot",
                            params=snap_params,
                            headers={"X-DX-Key": DX_API_KEY},
                            timeout=10
                        )
                        if snap.status_code == 200:
                            snap_data = snap.json()
                            content = snap_data.get("content", "")
                            new_hash = snap_data.get("content_hash")
                            # Decrypt before writing to disk
                            if content and (snap_data.get("is_encrypted") or str(content).startswith("vault_")):
                                active_secret = os.getenv("DX_TEAM_SECRET") or DX_TEAM_SECRET
                                if active_secret:
                                    try: content = NeuralVault.unlock(content, active_secret)
                                    except Exception as dec_err:
                                        return f"[!] Accept failed: could not decrypt content — check DX_TEAM_SECRET ({dec_err})"
                            if content:
                                # Use in-memory pause — no filesystem lock needed
                                import __main__
                                h = getattr(__main__, "_live_handler", None)
                                if h: h.pause_path(file_path)
                                try:
                                    with open(file_path, "w", encoding="utf-8") as f:
                                        f.write(content)
                                    if new_hash:
                                        if h:
                                            h.last_hashes[file_path] = new_hash
                                            h.content_cache[file_path] = content
                                            h.save_hashes()
                                        else:
                                            states_path = os.path.join(".dalexor", "states.json")
                                            hashes = {}
                                            if os.path.exists(states_path):
                                                with open(states_path, 'r') as f:
                                                    try: d = json.load(f); hashes = d.get("hashes", {})
                                                    except: pass
                                            hashes[file_path] = new_hash
                                            with open(states_path, 'w') as f:
                                                json.dump({"hashes": hashes, "ts": time.time()}, f)
                                finally:
                                    if h: h.resume_path(file_path)
                                return f"[+] Successfully integrated remote evolution: {os.path.basename(file_path)}"
                    except Exception as e:
                        return f"[+] Accepted by Cloud, but local write failed: {e}"
                return str(data)
            return f"Error: {resp.status_code} - {resp.text}"
    except Exception as e:
        return f"Transmission Failure: {e}"

    # Legacy fallback - try cloud_call
    res = await cloud_call("accept_incoming_change", {"file_path": file_path})
    # If the server says "Accepted", we force a local refresh from snapshot
    if isinstance(res, str) and ("Accepted" in res or "ok" in res.lower()):
        try:
            # Fetch the actual content live from snapshot
            async with httpx.AsyncClient() as client:
                params = {"file_path": file_path}
                if os.getenv("DX_PROJECT_ID"): params["project_id"] = os.getenv("DX_PROJECT_ID")
                snap = await client.get(
                    f"{CLOUD_URL}/file-snapshot",
                    params=params,
                    headers={"X-DX-Key": DX_API_KEY},
                    timeout=10
                )
                if snap.status_code == 200:
                    data = snap.json()
                    content = data.get("content", "")
                    new_hash = data.get("content_hash")
                    if content:
                        # Use in-memory pause — no filesystem lock needed
                        import __main__
                        h = getattr(__main__, "_live_handler", None)
                        if h: h.pause_path(file_path)
                        try:
                            with open(file_path, "w", encoding="utf-8") as f:
                                f.write(content)
                            if new_hash:
                                if h:
                                    h.last_hashes[file_path] = new_hash
                                    h.content_cache[file_path] = content
                                    h.save_hashes()
                                else:
                                    states_path = os.path.join(".dalexor", "states.json")
                                    hashes = {}
                                    if os.path.exists(states_path):
                                        with open(states_path, 'r') as f:
                                            try: d = json.load(f); hashes = d.get("hashes", {})
                                            except: pass
                                    hashes[file_path] = new_hash
                                    with open(states_path, 'w') as f:
                                        json.dump({"hashes": hashes, "ts": time.time()}, f)
                        finally:
                            if h: h.resume_path(file_path)
                        return f"[+] Successfully integrated remote evolution: {os.path.basename(file_path)}"
        except Exception as e:
            return f"[!] Accepted by Cloud, but local write failed: {e}"
    return res

async def reject_incoming_change(file_path: str, review_id: str = None):
    """Call the /review/reject endpoint and advance local hash baseline."""
    import httpx
    project_id = os.getenv("DX_PROJECT_ID")
    try:
        async with httpx.AsyncClient() as client:
            payload = {}
            if review_id:
                payload["review_id"] = review_id
            elif file_path:
                payload["file_path"] = file_path
            else:
                return "Error: Either review_id or file_path required"

            if project_id:
                payload["project_id"] = project_id

            resp = await client.post(
                f"{CLOUD_URL}/review/reject",
                json=payload,
                headers={"X-DX-Key": DX_API_KEY},
                timeout=15.0
            )
            if resp.status_code == 200:
                result = resp.json()
                # Advance the local hash baseline so the next push isn't blocked
                # by the same conflict again.
                server_hash = result.get("server_hash")
                if server_hash and file_path:
                    try:
                        h = globals().get("handler") or CodeChangeHandler(None)
                        h.last_hashes[file_path] = server_hash
                        h.save_hashes()
                    except Exception:
                        pass
                return result
            return f"Error: {resp.status_code} - {resp.text}"
    except Exception as e:
        return f"Transmission Failure: {e}"

async def merge_incoming_change(file_path: str, resolved_content: str):
    """Call the /review/merge endpoint directly."""
    import httpx
    project_id = os.getenv("DX_PROJECT_ID")
    try:
        async with httpx.AsyncClient() as client:
            payload = {
                "file_path": file_path,
                "resolved_content": resolved_content
            }
            if project_id:
                payload["project_id"] = project_id
            
            resp = await client.post(
                f"{CLOUD_URL}/review/merge",
                json=payload,
                headers={"X-DX-Key": DX_API_KEY},
                timeout=15.0
            )
            if resp.status_code == 200:
                return resp.json()
            return f"Error: {resp.status_code} - {resp.text}"
    except Exception as e:
        return f"Transmission Failure: {e}"


def dx_review_popup(pending):
    """Review popup — shows full file content for pushes, diff for conflicts."""
    import tkinter as tk
    from tkinter import scrolledtext, messagebox, font as tkfont
    import threading, asyncio, queue, requests as _req

    diff_cache   = {}   # path -> text to display
    preview_queue = queue.Queue()
    paths = list(pending.keys())

    # ── Colours & fonts ──────────────────────────────────────────────────────
    BG        = "#0d0d10"
    SURFACE   = "#16161d"
    SURFACE2  = "#1e1e28"
    BORDER    = "#2a2a38"
    ACCENT    = "#7c6af7"      # indigo
    ACCENT2   = "#5eead4"      # teal
    SUCCESS   = "#22c55e"
    DANGER    = "#ef4444"
    WARN      = "#f59e0b"
    TXT       = "#e2e8f0"
    TXT_DIM   = "#64748b"
    TXT_MID   = "#94a3b8"
    PLUS_CLR  = "#4ade80"
    MINUS_CLR = "#f87171"
    HDR_CLR   = "#818cf8"

    # ── Prefetcher: always fetch /file-snapshot for every path ───────────────
    def prefetcher():
        pid = os.getenv("DX_PROJECT_ID")
        for path in paths:
            try:
                item       = pending.get(path, {})
                is_conflict = item.get("is_conflict", False) or "CONFLICT" in item.get("diff","").upper()
                params = {"file_path": path}
                if pid: params["project_id"] = pid
                # Pass atom_id so we show the specific teammate version, not our own latest
                item_atom_id = item.get("atom_id")
                if item_atom_id: params["atom_id"] = item_atom_id
                resp = _req.get(f"{CLOUD_URL}/file-snapshot",
                                params=params,
                                headers={"X-DX-Key": DX_API_KEY},
                                timeout=12)
                if resp.status_code == 200:
                    data    = resp.json()
                    content = data.get("content", "")
                    if data.get("is_encrypted") and DX_TEAM_SECRET:
                        try: content = NeuralVault.unlock(content, DX_TEAM_SECRET)
                        except: pass
                    if is_conflict:
                        diff_cache[path] = ("conflict", content or item.get("diff","[no content]"))
                    else:
                        diff_cache[path] = ("push", content or "[no content]")
                else:
                    diff_cache[path] = ("error", f"[server returned {resp.status_code}]")
            except Exception as e:
                diff_cache[path] = ("error", f"[fetch failed: {e}]")
            preview_queue.put(path)

    threading.Thread(target=prefetcher, daemon=True).start()

    # ── Root window ──────────────────────────────────────────────────────────
    root = tk.Tk()
    root.title("Dalexor MI — Review")
    root.geometry("1280x820")
    root.configure(bg=BG)
    root.resizable(True, True)

    try:
        root.tk.call("tk", "scaling", 1.25)
    except: pass

    # ── Left panel: file list ─────────────────────────────────────────────────
    left = tk.Frame(root, bg=SURFACE, width=260)
    left.pack(side=tk.LEFT, fill=tk.Y)
    left.pack_propagate(False)

    # Header strip
    hdr_strip = tk.Frame(left, bg=ACCENT, height=3)
    hdr_strip.pack(fill=tk.X)

    tk.Label(left, text="PENDING UPDATES", bg=SURFACE, fg=ACCENT,
             font=("Segoe UI", 9, "bold"), pady=16).pack(fill=tk.X, padx=16)

    list_frame = tk.Frame(left, bg=SURFACE)
    list_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=(0,12))

    file_list = tk.Listbox(list_frame, bg=SURFACE, fg=TXT, selectbackground=ACCENT,
                           selectforeground="#fff", border=0, highlightthickness=0,
                           font=("Segoe UI", 11), activestyle="none",
                           selectmode=tk.SINGLE, relief=tk.FLAT)
    file_list.pack(fill=tk.BOTH, expand=True)

    for p in paths:
        item = pending.get(p, {})
        badge = " ⚠" if item.get("is_conflict") else " ↑"
        file_list.insert(tk.END, f"  {badge}  {os.path.basename(p)}")

    # ── Right panel ───────────────────────────────────────────────────────────
    right = tk.Frame(root, bg=BG)
    right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

    # Top bar
    topbar = tk.Frame(right, bg=SURFACE2, pady=0)
    topbar.pack(fill=tk.X)

    accent_line = tk.Frame(topbar, bg=ACCENT, height=3)
    accent_line.pack(fill=tk.X)

    topbar_inner = tk.Frame(topbar, bg=SURFACE2, padx=24, pady=14)
    topbar_inner.pack(fill=tk.X)

    title_label = tk.Label(topbar_inner, text="Select a file to review",
                           font=("Segoe UI", 16, "bold"), bg=SURFACE2, fg=TXT, anchor=tk.W)
    title_label.pack(fill=tk.X)

    info_label = tk.Label(topbar_inner, text="",
                          font=("Segoe UI", 9), bg=SURFACE2, fg=TXT_MID, anchor=tk.W)
    info_label.pack(fill=tk.X, pady=(4,0))

    # Badge row
    badge_frame = tk.Frame(topbar_inner, bg=SURFACE2)
    badge_frame.pack(fill=tk.X, pady=(8,0))

    type_badge  = tk.Label(badge_frame, text="", font=("Segoe UI", 8, "bold"),
                           bg=SURFACE2, fg=ACCENT, padx=0)
    type_badge.pack(side=tk.LEFT)

    # Content area
    content_outer = tk.Frame(right, bg=SURFACE, padx=2, pady=2)
    content_outer.pack(fill=tk.BOTH, expand=True, padx=20, pady=(16,0))

    diff_view = scrolledtext.ScrolledText(
        content_outer, bg="#0a0a0f", fg=TXT,
        font=("JetBrains Mono", 10) if tkfont.families().__contains__("JetBrains Mono")
              else ("Consolas", 10),
        borderwidth=0, relief=tk.FLAT,
        padx=18, pady=16,
        insertbackground="white",
        wrap=tk.NONE,
        selectbackground=ACCENT,
    )
    diff_view.pack(fill=tk.BOTH, expand=True)
    diff_view.config(state=tk.DISABLED)

    # Horizontal scrollbar
    hbar = tk.Scrollbar(content_outer, orient=tk.HORIZONTAL, command=diff_view.xview)
    hbar.pack(fill=tk.X)
    diff_view.config(xscrollcommand=hbar.set)

    # ── Bottom action bar ──────────────────────────────────────────────────────
    bottom = tk.Frame(right, bg=SURFACE2, padx=20, pady=14)
    bottom.pack(fill=tk.X)

    def mk_btn(parent, text, bg, command, side=tk.LEFT, padx=6):
        b = tk.Button(parent, text=text, bg=bg, fg="#fff",
                      font=("Segoe UI", 10, "bold"),
                      relief=tk.FLAT, padx=22, pady=9,
                      cursor="hand2", activebackground=bg,
                      activeforeground="#fff", bd=0,
                      command=command)
        b.pack(side=side, padx=padx)
        # Hover effect
        b.bind("<Enter>", lambda e: b.config(bg=_lighten(bg)))
        b.bind("<Leave>", lambda e: b.config(bg=bg))
        return b

    def _lighten(hex_color):
        """Return a slightly lighter shade of hex_color."""
        try:
            r = int(hex_color[1:3],16); g = int(hex_color[3:5],16); b2 = int(hex_color[5:7],16)
            r = min(255, r+30); g = min(255, g+30); b2 = min(255, b2+30)
            return f"#{r:02x}{g:02x}{b2:02x}"
        except: return hex_color

    reject_btn = mk_btn(bottom, "✕  REJECT",  DANGER,   lambda: process_review("reject"), tk.LEFT)
    accept_btn = mk_btn(bottom, "✓  ACCEPT",  SUCCESS,  lambda: process_review("accept"), tk.RIGHT)

    # Status label in the middle
    status_label = tk.Label(bottom, text="", font=("Segoe UI", 9),
                            bg=SURFACE2, fg=TXT_MID)
    status_label.pack(side=tk.LEFT, padx=12)

    # ── Helpers ───────────────────────────────────────────────────────────────
    def render_content(path, kind, text):
        diff_view.config(state=tk.NORMAL)
        diff_view.delete(1.0, tk.END)

        diff_view.tag_configure("plus",    foreground=PLUS_CLR)
        diff_view.tag_configure("minus",   foreground=MINUS_CLR)
        diff_view.tag_configure("hdr",     foreground=HDR_CLR,  font=("Consolas",10,"bold"))
        diff_view.tag_configure("meta",    foreground=ACCENT2)
        diff_view.tag_configure("keyword", foreground="#c084fc")
        diff_view.tag_configure("string",  foreground="#86efac")
        diff_view.tag_configure("comment", foreground=TXT_DIM)
        diff_view.tag_configure("number",  foreground="#fb923c")

        lines = text.splitlines()
        for i, line in enumerate(lines):
            ln = f"{line}\n"
            row = i + 1
            diff_view.insert(tk.END, ln)
            if kind == "conflict":
                if line.startswith("+") and not line.startswith("+++"):
                    diff_view.tag_add("plus",  f"{row}.0", f"{row}.end")
                elif line.startswith("-") and not line.startswith("---"):
                    diff_view.tag_add("minus", f"{row}.0", f"{row}.end")
                elif line.startswith("@@") or line.startswith("###"):
                    diff_view.tag_add("hdr",   f"{row}.0", f"{row}.end")
                elif line.startswith("---") or line.startswith("+++"):
                    diff_view.tag_add("meta",  f"{row}.0", f"{row}.end")
            else:
                # Light syntax highlight for push content
                import re as _re
                stripped = line.lstrip()
                if stripped.startswith("#"):
                    diff_view.tag_add("comment", f"{row}.0", f"{row}.end")
                elif _re.match(r"^\s*(def |class |import |from |async def |return |if |else|elif |for |while |try:|except|with )", line):
                    # Find keyword span
                    m = _re.match(r"^(\s*)(def |class |import |from |async def |return |if |else:|elif |for |while |try:|except|with )", line)
                    if m:
                        kw_start = len(m.group(1))
                        kw_end   = kw_start + len(m.group(2))
                        diff_view.tag_add("keyword", f"{row}.{kw_start}", f"{row}.{kw_end}")

        diff_view.config(state=tk.DISABLED)
        diff_view.see("1.0")

    def build_info_text(path):
        item = pending.get(path, {})
        by   = item.get("pushed_by_email") or item.get("updated_by","teammate")
        ts   = (item.get("received_at") or item.get("created_at",""))[:19].replace("T"," ")
        msg  = item.get("commit_message","") or ""
        parts = [f"by  {by}"]
        if msg: parts.append(f'"{msg}"')
        if ts:  parts.append(ts)
        return "  ·  ".join(parts)

    def update_topbar(path):
        item = pending.get(path, {})
        is_conf = item.get("is_conflict", False)
        title_label.config(text=os.path.basename(path))
        info_label.config(text=build_info_text(path))
        if is_conf:
            type_badge.config(text="⚠  CONFLICT — a teammate changed this file",
                              fg=WARN)
            accent_line.config(bg=WARN)
        else:
            type_badge.config(text="↑  INCOMING PUSH",
                              fg=ACCENT2)
            accent_line.config(bg=ACCENT)

    def on_select(event):
        try:
            idx = file_list.curselection()
            if not idx: return
            path = paths[idx[0]]
            update_topbar(path)
            if path in diff_cache:
                kind, text = diff_cache[path]
                render_content(path, kind, text)
                status_label.config(text="")
            else:
                diff_view.config(state=tk.NORMAL)
                diff_view.delete(1.0, tk.END)
                diff_view.insert(tk.END, "\n\n    Fetching from Global Brain…")
                diff_view.config(state=tk.DISABLED)
                status_label.config(text="Loading…")
        except: pass

    file_list.bind("<<ListboxSelect>>", on_select)

    _window_alive = [True]
    _after_id = [None]

    def check_queue():
        if not _window_alive[0]:
            return
        try:
            while True:
                path = preview_queue.get_nowait()
                idx  = file_list.curselection()
                if idx and paths[idx[0]] == path and path in diff_cache:
                    kind, text = diff_cache[path]
                    render_content(path, kind, text)
                    update_topbar(path)
                    status_label.config(text="")
        except queue.Empty: pass
        if _window_alive[0]:
            _after_id[0] = root.after(80, check_queue)

    _after_id[0] = root.after(80, check_queue)

    def _on_close():
        _window_alive[0] = False
        if _after_id[0]:
            try: root.after_cancel(_after_id[0])
            except: pass
        root.destroy()

    root.protocol("WM_DELETE_WINDOW", _on_close)

    # Auto-select first file
    if paths:
        file_list.selection_set(0)
        file_list.event_generate("<<ListboxSelect>>")

    # ── Actions ───────────────────────────────────────────────────────────────
    def process_review(mode):
        idx = file_list.curselection()
        if not idx: return
        list_idx = idx[0]
        path     = paths[list_idx]
        review_id = pending.get(path, {}).get("review_id")

        reject_btn.config(state=tk.DISABLED)
        accept_btn.config(state=tk.DISABLED)
        status_label.config(text="Processing…")

        def run():
            try:
                loop = asyncio.new_event_loop()
                if mode == "accept":
                    res = loop.run_until_complete(accept_incoming_change(path, review_id))
                else:
                    res = loop.run_until_complete(reject_incoming_change(path, review_id))
                root.after(0, lambda r=res, l=list_idx: finish(r, l))
            except Exception as e:
                err = str(e)
                root.after(0, lambda: (
                    reject_btn.config(state=tk.NORMAL),
                    accept_btn.config(state=tk.NORMAL),
                    status_label.config(text=f"Error: {err[:80]}"),
                    messagebox.showerror("Error", err)
                ))

        def finish(msg, li):
            # Show result briefly in status bar instead of blocking messagebox
            verb = "accepted" if mode == "accept" else "rejected"
            status_label.config(text=f"✓ {verb}: {os.path.basename(path)}")
            file_list.delete(li)
            paths.pop(li)
            diff_view.config(state=tk.NORMAL)
            diff_view.delete(1.0, tk.END)
            diff_view.config(state=tk.DISABLED)
            title_label.config(text="Select a file to review")
            info_label.config(text="")
            type_badge.config(text="")
            accent_line.config(bg=ACCENT)
            reject_btn.config(state=tk.NORMAL)
            accept_btn.config(state=tk.NORMAL)
            if not paths:
                root.after(800, _on_close)  # brief pause so user sees the status
            else:
                file_list.selection_set(0)
                file_list.event_generate("<<ListboxSelect>>")

        threading.Thread(target=run, daemon=True).start()

    root.mainloop()

if __name__ == "__main__":
    run_agent()