#!/usr/bin/env python3
import os
import sys
import time
import json
import argparse
import requests
import subprocess
import re
import shutil
import atexit
from collections import deque

# Ajout du dossier local lib pour les dépendances (ddgs)
if os.path.exists('lib'):
    sys.path.append(os.path.join(os.getcwd(), 'lib'))

try:
    from ddgs import DDGS
    DDGS_AVAILABLE = True
except ImportError:
    DDGS = None
    DDGS_AVAILABLE = False


from rich.console import Console
from rich.panel import Panel
from rich.markdown import Markdown
from rich.text import Text
from rich.live import Live
from rich.layout import Layout
from rich.spinner import Spinner

console = Console()

try:
    import readline
except ImportError:
    readline = None

class QuotaManager:
    def __init__(self, limits):
        self.limits = limits
        self.requests = deque() # Timestamps
        self.daily_requests = deque() # Timestamps
        self.tokens_per_minute = deque() # (timestamp, num_tokens)

    def get_stats(self):
        now = time.time()
        # Nettoyage RPD (dernières 24h)
        while self.daily_requests and self.daily_requests[0] < now - 86400:
            self.daily_requests.popleft()
        
        return {
            "RPD": f"{len(self.daily_requests)}/{self.limits.get('RPD', 'inf')}",
            "RPM": f"{len(self.requests)}/{self.limits.get('RPM', 'inf')}"
        }

    def wait_if_rpm_reached(self):
        now = time.time()
        while self.requests and self.requests[0] < now - 60:
            self.requests.popleft()
        
        if len(self.requests) >= self.limits.get('RPM', float('inf')):
            wait_time = 60 - (now - self.requests[0])
            if wait_time > 0:
                print(f"\n[!] RPM atteint. Attente de {int(wait_time)}s...")
                time.sleep(wait_time)
                self.wait_if_rpm_reached()

    def can_make_request(self, num_tokens):
        now = time.time()
        
        # Nettoyage
        while self.daily_requests and self.daily_requests[0] < now - 86400:
            self.daily_requests.popleft()
        while self.tokens_per_minute and self.tokens_per_minute[0][0] < now - 60:
            self.tokens_per_minute.popleft()

        # Vérification RPD
        if len(self.daily_requests) >= self.limits.get('RPD', float('inf')):
            return False, "Limite RPD journalière atteinte."
        
        # Vérification TPM
        current_tpm = sum(t[1] for t in self.tokens_per_minute)
        if current_tpm + num_tokens > self.limits.get('TPM', float('inf')):
            return False, "Limite TPM atteinte."
            
        return True, ""

    def record_request(self, num_tokens):
        now = time.time()
        self.requests.append(now)
        self.daily_requests.append(now)
        self.tokens_per_minute.append((now, num_tokens))

# --- GESTIONNAIRE D'INTERFACE TERMINAL ---
class TerminalUIManager:
    def __init__(self):
        self.current_status = ""
        self.header_text = "ALBERT AGENT CLI"
        self.setup()
        atexit.register(self.cleanup)

    def setup(self):
        self.cols, self.lines = shutil.get_terminal_size()
        sys.stdout.write("\033[2J\033[H") # Nettoie l'écran et curseur en haut
        
        self.draw_header()
        
        # Zone de défilement (lignes 4 à L-1) : Histoire + Input
        # La ligne L est réservée au statut.
        sys.stdout.write(f"\033[4;{self.lines-1}r")
        sys.stdout.write(f"\033[{self.lines-1};1H")
        sys.stdout.flush()

    def cleanup(self):
        sys.stdout.write("\033[r") # Réinitialise la zone de défilement
        sys.stdout.write("\033[2J\033[H")
        sys.stdout.flush()

    def draw_header(self):
        sys.stdout.write("\0337") # Sauvegarde le curseur
        sys.stdout.write("\033[1;1H") # En haut à gauche
        
        header_panel = Panel(
            Text(self.header_text, justify="center", style="bold white on blue"),
            style="blue",
            padding=(0, 0),
            expand=True
        )
        with console.capture() as capture:
            console.print(header_panel)
        
        header_str = capture.get()
        # On ne prend que les 3 premières lignes pour le header
        lines = header_str.splitlines()[:3]
        for i, line in enumerate(lines):
            sys.stdout.write(f"\033[{i+1};1H\033[2K{line}")
            
        sys.stdout.write("\0338") # Restaure le curseur
        sys.stdout.flush()

    def update_status(self, text):
        self.current_status = text
        self.cols, self.lines = shutil.get_terminal_size()
        sys.stdout.write("\0337") # Sauvegarde le curseur
        
        # Ligne de statut (L) tout en bas, hors zone de scroll
        sys.stdout.write(f"\033[{self.lines};1H\033[2K") 
        
        # On utilise une version plus simple pour éviter les conflits de rendu dans la barre de statut
        # On nettoie le texte des balises rich pour le calcul de la longueur si besoin, 
        # mais ici on va juste utiliser console.render pour être sûr.
        status_text = Text.from_markup(f"[bold white on blue] STATUS [/] {text}")
        status_text.truncate(self.cols)
        status_text.pad_right(self.cols)
        
        # Utilisation de couleurs inversées ANSI pour forcer la visibilité de la barre
        sys.stdout.write("\033[7m") 
        with console.capture() as capture:
            console.print(status_text, end="")
        sys.stdout.write(capture.get())
        sys.stdout.write("\033[0m")
        
        sys.stdout.write("\0338") # Restaure le curseur
        sys.stdout.flush()

    def get_input(self, prompt_text):
        self.cols, self.lines = shutil.get_terminal_size()
        
        # S'assure que le fenêtrage est ok
        sys.stdout.write(f"\033[4;{self.lines-1}r")
        self.update_status(self.current_status)
        self.draw_header()
        
        # On se place à la dernière ligne de la zone de scroll
        sys.stdout.write(f"\033[{self.lines - 1};1H\033[2K") 
        
        # Colorisation de l'invite avec gestion des caractères non-imprimables pour readline
        rich_prompt = f"[bold cyan]{prompt_text}[/]"
        with console.capture() as capture:
            console.print(rich_prompt, end="")
        
        ansi_prompt = capture.get()
        # On enveloppe les séquences ANSI pour que readline ne compte pas leur largeur
        # \x01 et \x02 sont les délimiteurs de début/fin de séquence non-imprimable pour readline
        # On utilise les caractères réels car re.sub ne supporte pas \x dans la chaîne de remplacement
        final_prompt = re.sub(r'(\x1b\[[0-9;]*[mK])', '\x01\\1\x02', ansi_prompt)
        
        try:
            val = input(final_prompt)
        except (EOFError, KeyboardInterrupt):
            val = "exit"
            
        return val

UI = None # Instance globale pour l'interface

# --- CONFIGURATION QUOTAS ---
LIMITS = {
    "openai/gpt-oss-120b": {"RPM": 50, "RPD": 5000, "TPM": 246000},
    "mistralai/Mistral-Small-3.2-24B-Instruct-2506": {"RPM": 100, "RPD": 50000, "TPM": 246000},
    "Qwen/Qwen3-Coder-30B-A3B-Instruct": {"RPM": 100, "RPD": 50000, "TPM": 246000},
}
quota_managers = {model: QuotaManager(lim) for model, lim in LIMITS.items()}

# --- COULEURS ---
C_RESET = "\033[0m"
C_BOLD = "\033[1m"
C_CYAN = "\033[36m"
C_GREEN = "\033[32m"
C_YELLOW = "\033[33m"
C_RED = "\033[31m"
C_BLUE = "\033[34m"

# --- CONFIGURATION ---
ALBERT_BASE_URL = "https://albert.api.etalab.gouv.fr/v1"
ENV_FILE = ".env"

def log_system(msg): console.print(f"[bold cyan][*][/] {msg}")
def log_warn(msg): console.print(f"[bold yellow][!][/] {msg}")
def log_err(msg): console.print(f"[bold red][!][/] {msg}")
def log_success(msg): console.print(f"[bold green][+][/] {msg}")

MODELES = {
    "chatDocument": "openai/gpt-oss-120b",
    "coder": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "chat": "mistralai/Mistral-Small-3.2-24B-Instruct-2506"
}

TOOL_INSTRUCTIONS = """
### CAPACITÉS AGENTIQUES (OUTILS)
Tu peux interagir avec le système en utilisant EXCLUSIVEMENT les balises suivantes. 
Tu ne dois PAS inventer d'autres balises comme [TOOL_CALLS].
Si tu utilises un outil, écris la balise et ARRÊTE ton message immédiatement.

Outils disponibles :
1. Lister les fichiers : [LIST_FILES]
2. Lire un fichier : [READ_FILE:chemin]
3. Créer/Écrire un fichier : [WRITE_FILE:chemin]contenu[/WRITE_FILE]
4. Exécuter une commande : [EXECUTE:commande]
5. Rechercher sur le web : [SEARCH:votre requête]

RÈGLES STRICTES :
- UN SEUL outil par message.
- Ne mets pas d'espace entre le nom du fichier et les balises dans WRITE_FILE.
- Attends la réponse du système après chaque outil.
"""

PERSONNALITES = {
    "chat": """
### RÔLE
Tu es Albert, un assistant intelligent et curieux.
### STYLE ET TON
- Réponses claires et amicales.
- Tu peux chercher sur internet si tu as besoin d'informations fraîches avec [SEARCH:requête].
""" + TOOL_INSTRUCTIONS,
    
    "coder": """
### RÔLE
Tu es un expert en programmation.
""" + TOOL_INSTRUCTIONS,

    "chatplus": """
### RÔLE
Tu es un assistant avancé capable d'analyser et de traiter des documents complexes.
""" + TOOL_INSTRUCTIONS
}

AVAILABLE_MODELS_CACHE = []

def load_api_key():
    if os.path.exists(ENV_FILE):
        with open(ENV_FILE, "r") as f:
            for line in f:
                if line.startswith("ALBERT_API_KEY2="):
                    return line.split("=")[1].strip()
    return os.environ.get("ALBERT_API_KEY2")

def list_files():
    try:
        files = os.listdir('.')
        return "\n".join(files)
    except Exception as e:
        return f"Erreur : {e}"

def is_path_safe(path):
    try:
        abs_path = os.path.realpath(path)
        current_dir = os.path.realpath(os.getcwd())
        return os.path.commonpath([abs_path, current_dir]) == current_dir
    except:
        return False

def read_file(path):
    try:
        path = path.strip().strip("'\"")
        if not is_path_safe(path):
            return "Erreur : Accès refusé (hors du répertoire de travail)."
        with open(path, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception as e:
        return f"Erreur de lecture : {e}"

def write_file(path, content):
    try:
        path = path.strip().strip("'\"")
        if not is_path_safe(path):
            return "Erreur : Écriture refusée (hors du répertoire de travail)."
        with open(path, 'w', encoding='utf-8') as f:
            f.write(content)
        return f"Succès : Fichier {path} écrit."
    except Exception as e:
        return f"Erreur d'écriture : {e}"

def run_command(cmd):
    try:
        forbidden_patterns = [
            "python -m http.server", "python3 -m http.server",
            "node server.js", "npm start", "npm run dev",
            "php -S", "ruby -run -e httpd"
        ]
        if any(pattern in cmd for pattern in forbidden_patterns):
            return "Erreur : Lancement de serveur HTTP interdit pour des raisons de sécurité."

        if "/" in cmd and not cmd.startswith("./") and not cmd.startswith("python") and not cmd.startswith("pip"):
            paths = re.findall(r"(/[^\s]+)", cmd)
            for p in paths:
                if not is_path_safe(p):
                    return f"Erreur : La commande semble utiliser un chemin non autorisé ({p})."

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
        return f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
    except Exception as e:
        return f"Erreur d'exécution : {e}"

def ask_confirmation(action):
    console.print(f"\n[bold yellow][?][/] ALBERT veut : {action}")
    choice = UI.get_input(f"Autoriser ? ([bold green]y[/]: oui, [bold red]n[/]: non, [bold green]a[/]: TOUJOURS, [bold green]all[/]: TOUT) : ").lower()
    return choice

def search_web(query):
    if not DDGS:
        return "Erreur : La bibliothèque 'ddgs' n'est pas installée."
    try:
        results = []
        with DDGS() as ddgs:
            for r in ddgs.text(query, max_results=5):
                results.append(f"Titre: {r['title']}\nLien: {r['href']}\nContenu: {r['body']}\n")
        return "\n".join(results) if results else "Aucun résultat trouvé."
    except Exception as e:
        return f"Erreur lors de la recherche : {e}"

def handle_tools(text, permissions):
    text = text.replace("[TOOL_CALLS]", "")

    if permissions.get("all"):
        permissions.update({"list": True, "read": True, "write": True, "execute": True, "search": True})

    if "[LIST_FILES]" in text:
        if permissions["list"]: return list_files()
        res = ask_confirmation("Lister les fichiers")
        if res == 'all': permissions["all"] = True; permissions["list"] = True; return list_files()
        if res == 'a': permissions["list"] = True; return list_files()
        if res == 'o' or res == 'y': return list_files()
        return "Action refusée."
        
    search_match = re.search(r"\[SEARCH:(.*?)\]", text)
    if search_match:
        query = search_match.group(1).strip()
        if permissions.get("search"): return search_web(query)
        res = ask_confirmation(f"Rechercher sur le web : '{query}'")
        if res == 'all': permissions["all"] = True; permissions["search"] = True; return search_web(query)
        if res == 'a': permissions["search"] = True; return search_web(query)
        if res == 'o' or res == 'y': return search_web(query)
        return "Action refusée."

    write_match = re.search(r"\[WRITE_FILE:(.*?)\](.*?)\[/WRITE_FILE\]", text, re.DOTALL)
    if write_match:
        path = write_match.group(1).strip()
        content = write_match.group(2)
        if permissions["write"]: return write_file(path, content)
        res = ask_confirmation(f"Écrire '{path}'")
        if res == 'all': permissions["all"] = True; permissions["write"] = True; return write_file(path, content)
        if res == 'a': permissions["write"] = True; return write_file(path, content)
        if res == 'o': return write_file(path, content)
        return "Action refusée."
    
    read_match = re.search(r"\[READ_FILE:(.*?)\]", text)
    if read_match:
        path = read_match.group(1).strip()
        if permissions["read"]: return read_file(path)
        res = ask_confirmation(f"Lire '{path}'")
        if res == 'all': permissions["all"] = True; permissions["read"] = True; return read_file(path)
        if res == 'a': permissions["read"] = True; return read_file(path)
        if res == 'o': return read_file(path)
        return "Action refusée."
    
    exec_match = re.search(r"\[EXECUTE:(.*?)\]", text)
    if exec_match:
        cmd = exec_match.group(1).strip()
        if permissions["execute"]: return run_command(cmd)
        res = ask_confirmation(f"Exécuter '{cmd}'")
        if res == 'all': permissions["all"] = True; permissions["execute"] = True; return run_command(cmd)
        if res == 'a': permissions["execute"] = True; return run_command(cmd)
        if res == 'o': return run_command(cmd)
        return "Action refusée."
    
    return None

def list_models_api(api_key):
    headers = {"Authorization": f"Bearer {api_key}"}
    try:
        response = requests.get(f"{ALBERT_BASE_URL}/models", headers=headers)
        response.raise_for_status()
        models = response.json().get("data", [])
        return [m['id'] for m in models]
    except:
        return []

class AlbertCompleter:
    def __init__(self, personas, models):
        self.personas = personas
        self.models = models
        self.commands = ["/mode", "/list", "/exit"]

    def complete(self, text, state):
        buffer = readline.get_line_buffer()
        if not " " in buffer:
            results = [c for c in self.commands if c.startswith(text)]
        else:
            parts = buffer.split()
            cmd = parts[0].lower()
            if cmd == "/mode":
                results = [p for p in self.personas if p.startswith(text)]
            else:
                results = []
        return results[state] if state < len(results) else None

def setup_readline(personas, models):
    if not readline: return
    completer = AlbertCompleter(personas, models)
    readline.set_completer(completer.complete)
    if 'libedit' in readline.__doc__:
        readline.parse_and_bind("bind ^I rl_complete")
    else:
        readline.parse_and_bind("tab: complete")
    readline.set_completer_delims(" \t\n")

def update_status_for_model(model):
    manager = quota_managers.get(model)
    if manager:
        stats = manager.get_stats()
        UI.update_status(f" [bold cyan]MODÈLE:[/] {model} | [bold green]RPM:[/] {stats['RPM']} | [bold green]RPD:[/] {stats['RPD']} ")
    else:
        UI.update_status(f" [bold cyan]MODÈLE:[/] {model} | [bold red]LIMITES:[/] Non définies ")

def call_albert_api(api_key, model, messages, stream=True, show_albert_prefix=True, silent=False):
    num_tokens = sum(len(msg["content"]) // 4 for msg in messages)
    
    manager = quota_managers.get(model)
    if manager:
        manager.wait_if_rpm_reached()
        can, reason = manager.can_make_request(num_tokens)
        if not can:
            if not silent: log_err(f"Quota {model} dépassé : {reason}")
            return None
    
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    payload = {"model": model, "messages": messages, "stream": stream, "temperature": 0.3}
    
    retries = 0
    max_retries = 5
    wait = 5
    
    while retries < max_retries:
        response = requests.post(f"{ALBERT_BASE_URL}/chat/completions", headers=headers, json=payload, stream=stream)
        
        if response.status_code == 429:
            if not silent: log_warn(f"Limite de jetons atteinte (429). Attente de {wait} secondes...")
            time.sleep(wait)
            retries += 1
            wait *= 2
            continue
        
        if response.status_code != 200:
            if not silent: console.print(f"\n[bold red]Erreur API ({response.status_code}): {response.text}[/]")
            return None
        
        break
    
    if retries == max_retries:
        if not silent: log_err("Trop d'erreurs de limite de jetons. Abandon.")
        return None

    full_response = ""
    max_chars = 10000
    
    if stream:
        if not silent:
            # On utilise Live pour afficher la réponse qui se construit en temps réel dans un Panel
            with Live(Panel(Markdown("..."), title="Albert", border_style="blue"), refresh_per_second=10) as live:
                try:
                    for line in response.iter_lines():
                        if len(full_response) > max_chars:
                            break
                        if line:
                            line_text = line.decode("utf-8")
                            if line_text.startswith("data: "):
                                data_str = line_text[6:]
                                if data_str == "[DONE]": break
                                try:
                                    data = json.loads(data_str)
                                    content = data["choices"][0]["delta"].get("content", "")
                                    full_response += content
                                    live.update(Panel(Markdown(full_response), title="Albert", border_style="blue"))
                                except: continue
                except KeyboardInterrupt:
                    pass
        else:
            # Mode silencieux : on récupère juste la réponse
            for line in response.iter_lines():
                if len(full_response) > max_chars: break
                if line:
                    line_text = line.decode("utf-8")
                    if line_text.startswith("data: "):
                        data_str = line_text[6:]
                        if data_str == "[DONE]": break
                        try:
                            data = json.loads(data_str)
                            full_response += data["choices"][0]["delta"].get("content", "")
                        except: continue
    else:
        try:
            full_response = response.json()["choices"][0]["message"]["content"]
            if not silent:
                console.print(Panel(Markdown(full_response), title="Albert", border_style="blue"))
        except:
            return None
            
    if manager:
        manager.record_request(num_tokens + (len(full_response) // 4))
        update_status_for_model(model)
    
    return full_response

def show_paged_content(content, title="Document"):
    """Affiche le contenu dans un pager (type less) tout en préservant l'UI après."""
    try:
        # On désactive temporairement la zone de scroll pour que le pager puisse utiliser tout l'écran
        sys.stdout.write("\033[r\033[2J\033[H")
        sys.stdout.flush()
        
        with console.pager(styles=True):
            console.print(Panel(Markdown(content), title=title, border_style="yellow"))
            
    finally:
        # On restaure l'interface
        if UI:
            UI.setup()
            UI.update_status(UI.current_status)

def summarize_session(api_key, model, messages):
    """Génère un résumé condensé de la session pour la persistance."""
    summary_prompt = "Résume de manière très concise les actions effectuées et l'état actuel du projet en te basant UNIQUEMENT sur l'historique fourni. N'UTILISE PAS tes outils. Ne dépasse pas 15 lignes."
    summary_messages = messages + [{"role": "user", "content": summary_prompt}]
    
    # On utilise un modèle rapide pour le résumé, de manière totalement silencieuse
    summary = call_albert_api(api_key, model, summary_messages, stream=False, show_albert_prefix=False, silent=True)
    if summary:
        with open("~session_summary.md", "w", encoding="utf-8") as f:
            f.write(summary)
        return summary
    return None

def chat(api_key, model, persona):
    global AVAILABLE_MODELS_CACHE
    current_model = model
    current_persona = persona
    
    session_permissions = {"list": False, "read": False, "write": False, "execute": False, "all": False}
    deferred_plan = None

    if not AVAILABLE_MODELS_CACHE:
        print("[*] Récupération de la liste des modèles pour la complétion...")
        ids = list_models_api(api_key)
        AVAILABLE_MODELS_CACHE = list(set(ids + list(MODELES.keys())))

    setup_readline(list(PERSONNALITES.keys()), AVAILABLE_MODELS_CACHE)
    
    system_prompt = PERSONNALITES.get(current_persona, PERSONNALITES["chat"])
    
    # Chargement du résumé de session si existant
    if os.path.exists("~session_summary.md"):
        with open("~session_summary.md", "r", encoding="utf-8") as f:
            summary = f.read()
        system_prompt += f"\n\n### RÉSUMÉ DES SESSIONS PRÉCÉDENTES\n{summary}"
        log_system("Résumé de session chargé.")

    messages = [{"role": "system", "content": system_prompt}]
    
    console.print(Panel(Text("Albert Agent CLI", justify="center", style="bold white"), title="Bienvenue", border_style="blue"))
    log_system(f"Modèle actuel : [bold cyan]{current_model}[/]")
    log_system(f"Mode actuel   : [bold cyan]{current_persona}[/]")
    print(f"\nCommandes disponibles :")
    print("  /mode <tab>    : Changer de mode")
    print("  /list          : Lister les modèles")
    print("  /plan          : Afficher le plan actuel")
    print("  exit           : Quitter")
    print("-" * 30 + "\n")

    update_status_for_model(current_model)

    while True:
        try:
            update_status_for_model(current_model)
            user_input = UI.get_input(f"[{current_persona}]>> ").strip()
            if not user_input: continue
            
            # Gestion du chargement différé du plan
            if deferred_plan:
                user_input = f"{user_input}\n\n[CONTEXTE] Voici le plan de travail déjà validé et en cours d'exécution :\n{deferred_plan}"
                deferred_plan = None
                log_system("Plan injecté dans la requête.")

            if user_input.startswith("/"):
                parts = user_input.split()
                cmd = parts[0].lower()
                if cmd == "/mode":
                    if len(parts) > 1:
                        new_persona = parts[1].lower()
                        if new_persona in PERSONNALITES:
                            current_persona = new_persona
                            system_prompt = PERSONNALITES[current_persona]
                            messages = [{"role": "system", "content": system_prompt}]
                            
                            new_model = MODELES.get(current_persona, MODELES["chat"])
                            if current_persona == "chatplus": new_model = MODELES["chatDocument"]
                            current_model = new_model
                            
                            log_system(f"Mode changé : [bold cyan]{current_persona}[/] (Modèle : [bold cyan]{current_model}[/])")
                            update_status_for_model(current_model)
                        else: log_warn(f"Modes : {', '.join(PERSONNALITES.keys())}")
                    else: log_warn("Usage: /mode <nom>")
                    continue
                elif cmd == "/list":
                    ids = list_models_api(api_key)
                    console.print(f"\n[bold cyan]=== Modèles disponibles ===[/]")
                    for i in ids: console.print(f"- {i}")
                    continue
                elif cmd == "/plan":
                    plan_file = "~plan.md"
                    if os.path.exists(plan_file):
                        with open(plan_file, "r", encoding="utf-8") as f:
                            plan_content = f.read()
                        show_paged_content(plan_content, title="Plan de travail")
                    else:
                        log_warn("Aucun plan actif.")
                    continue
                elif cmd == "/exit": break
                else:
                    log_warn(f"Inconnu : {cmd}")
                    continue
            if user_input.lower() in ["exit", "quit"]: break

            if current_persona == "coder":
                plan_file = "~plan.md"
                use_existing = False
                
                if os.path.exists(plan_file):
                    reuse = UI.get_input(f"\n[bold yellow][?][/] Un plan (~plan.md) existe déjà. Le reprendre ? ([bold green]y[/]/[bold red]n[/]) : ").lower()
                    if reuse == 'y':
                        with open(plan_file, "r", encoding="utf-8") as f:
                            deferred_plan = f.read()
                        use_existing = True
                        log_system("Plan conservé pour la prochaine requête.")
                        continue # On attend la requête utilisateur
                
                if not use_existing:
                    with Live(Spinner("dots", text=Text("Albert réfléchit au plan...", style="bold yellow")), transient=True) as live:
                        log_system(f"Planification avec [bold cyan]{MODELES['chatDocument']}[/]...")
                        planning_messages = [
                            {"role": "system", "content": "Tu es un planificateur expert. Établis un plan précis pour la demande de l'utilisateur."},
                            {"role": "user", "content": user_input}
                        ]
                        # On passe silent=True pour ne pas polluer l'écran pendant l'animation
                        plan = call_albert_api(api_key, MODELES["chatDocument"], planning_messages, stream=False, show_albert_prefix=False, silent=True)
                    
                    if not plan:
                        log_err("Échec de la planification.")
                        continue
                    
                    with open(plan_file, "w", encoding="utf-8") as f:
                        f.write(plan)
                    log_success(f"Plan sauvegardé dans [bold cyan]{plan_file}[/]")
                    
                    # Affiche le plan pour validation via le pager
                    show_paged_content(plan, title="Plan proposé")
                    
                    update_status_for_model(MODELES["chatDocument"])
                    
                    confirm = UI.get_input(f"\n[bold yellow][?][/] Valider ce plan ? ([bold green]y[/]/[bold red]n[/]/[bold green]a[/]: TOUJOURS autoriser l'écriture, [bold green]all[/]: TOUJOURS TOUT autoriser) : ").lower()
                    if confirm == 'all':
                        session_permissions["all"] = True
                        confirm = 'y'
                    elif confirm == 'a':
                        session_permissions["write"] = True
                        confirm = 'y'
                    
                    if confirm != 'y':
                        if os.path.exists(plan_file):
                            os.remove(plan_file)
                        log_warn("Plan rejeté et supprimé.")
                        continue
                    
                    user_input = f"Voici le plan validé à exécuter. Applique-le DIRECTEMENT en utilisant tes outils. Ne redemande PAS de confirmation pour les étapes prévues dans ce plan.\n\nPLAN :\n{plan}"

            messages.append({"role": "user", "content": user_input})
            
            while True:
                full_response = call_albert_api(api_key, current_model, messages)
                if full_response is None: break
                
                messages.append({"role": "assistant", "content": full_response})
                
                # Sauvegarde du résumé après chaque interaction assistant
                summarize_session(api_key, MODELES["chat"], messages)

                tool_result = handle_tools(full_response, session_permissions)
                if tool_result:
                    messages.append({"role": "user", "content": f"[SYSTEME] Résultat de l'outil : {tool_result}"})
                    continue 
                else: 
                    if current_persona == "coder":
                        update_status_for_model(MODELES["coder"])
                    break
        except KeyboardInterrupt:
            print("\nInterrompu.")
            break
        except Exception as e:
            print(f"\nErreur : {e}")
            break
        except KeyboardInterrupt:
            print("\nInterrompu.")
            break
        except Exception as e:
            print(f"\nErreur : {e}")
            break

def main():
    global UI
    parser = argparse.ArgumentParser()
    parser.add_argument("--persona", choices=PERSONNALITES.keys(), default="chat")
    parser.add_argument("--model")
    args = parser.parse_args()
    
    api_key = load_api_key()
    if not api_key:
        print("Erreur: ALBERT_API_KEY2 manquante.")
        sys.exit(1)
        
    model_to_use = args.model or (MODELES["coder"] if args.persona == "coder" else MODELES["chat"])
    if not DDGS_AVAILABLE:
        print(f"\n{C_YELLOW}[!] ATTENTION : La bibliothèque 'duckduckgo_search' est manquante.{C_RESET}")
        print(f"    La recherche internet sera désactivée.")
        print(f"    Pour l'installer : {C_CYAN}pip install ddgs -t ./lib{C_RESET}")
        input("\nAppuyez sur [Entrée] pour continuer quand même...")
    # Initialisation de l'interface graphique terminal
    UI = TerminalUIManager()
    if not DDGS_AVAILABLE:
        log_warn("Mode recherche désactivé (ddgs manquant)")
    chat(api_key, model_to_use, args.persona)

if __name__ == "__main__":
    main()
