"""
candy.core — Interface universelle v16
======================================
Nouveautés v16 :
- CLI étendu : candy --score, --fix, --dry-run, --pipeline
- Visualisation ASCII pipeline dans terminal
- Memory.share() — collaboration multi-utilisateurs
- Candy.auto() — auto-sélection séquence multi-agents
- Dashboard insights depuis logs
- Candy.monitor() — stats en temps réel
- Candy.chain() — raccourci pipeline fluide
- CandyError — gestion d'erreur centralisée
"""

import json
import re
import time
import os
import copy
import hashlib
from pathlib import Path
from typing import Optional, Union
from .client import _client
from .config import get_profile


# ── Détection auto de personnalité ───────────────────────────────────────────

_AUTO_KEYWORDS = {
    "coding":       ["code","script","python","javascript","fonction","class","bug","erreur","algorithm","programme","def ","import ","debugge","syntax"],
    "math":         ["calcul","équation","intégrale","dérivée","math","formule","résoudre","algèbre","probabilité","statistique","matrix","vecteur"],
    "writing":      ["écris","rédige","article","texte","roman","poème","email","lettre","histoire","rédaction","essai","blog","script"],
    "cooking":      ["recette","cuisine","plat","ingrédient","cuisson","repas","menu","chef","gastronomie","prépare"],
    "translator":   ["traduis","translate","traduction","traduire","version anglaise","version française","en anglais","en espagnol"],
    "summarizer":   ["résume","résumé","synthèse","synthétise","summary","raccourcis","condense","en bref"],
    "medicine":     ["symptôme","médecin","maladie","santé","traitement","médicament","douleur","diagnostic","médical"],
    "finance":      ["investissement","bourse","budget","argent","finance","économie","action","portefeuille","crypto","trading"],
    "law":          ["loi","juridique","contrat","droit","légal","avocat","tribunal","réglementation","gdpr"],
    "science":      ["physique","chimie","biologie","astronomie","science","expérience","atome","molécule","énergie"],
    "history":      ["histoire","historique","guerre","siècle","époque","civilisation","empire","révolution","dates"],
    "marketing":    ["marketing","marque","campagne","publicité","audience","conversion","croissance","funnel"],
    "seo":          ["seo","référencement","google","mots-clés","backlink","classement","trafic","serp"],
    "youtube":      ["youtube","vidéo","chaîne","miniature","abonnés","vues","creator","thumbnail"],
    "socialmedia":  ["instagram","tiktok","linkedin","twitter","réseaux sociaux","followers","post","reel","viral"],
    "devops":       ["docker","kubernetes","ci/cd","déploiement","cloud","aws","pipeline","conteneur","nginx","terraform"],
    "mlops":        ["machine learning","modèle","ml","deep learning","neural","ia","entraînement","dataset","accuracy"],
    "prompt":       ["prompt","llm","chatgpt","instruction","chain","few-shot","optimise ce prompt","system prompt"],
    "nutrition":    ["nutrition","alimentation","régime","calories","vitamines","protéines","diète","macros"],
    "yoga":         ["yoga","méditation","posture","pranayama","bien-être","relaxation","mindfulness"],
    "leadership":   ["leadership","management","équipe","manager","diriger","motivation","organisation","team"],
    "branding":     ["marque","branding","identité","logo","positionnement","naming","brand","charte"],
    "debugger":     ["bug","erreur","traceback","fix","corrige","exception","crash","TypeError","ValueError"],
}

def _detect_personality(prompt: str) -> str:
    p = prompt.lower()
    scores = {}
    for personality, keywords in _AUTO_KEYWORDS.items():
        scores[personality] = sum(1 for kw in keywords if kw in p)
    best = max(scores, key=scores.get)
    return best if scores[best] > 0 else "full"

def _detect_best_sequence(task: str) -> list:
    """
    Analyse une tâche et retourne la séquence multi-agents optimale.
    Utilisé par Candy.auto()
    """
    t = task.lower()
    # Détecte le type de tâche
    if any(w in t for w in ["code","script","fonction","programme","app","api"]):
        return ["coding", "debugger", "reviewer"]
    elif any(w in t for w in ["article","blog","texte","rédige","écris"]):
        return ["writing", "reviewer", "summarizer"]
    elif any(w in t for w in ["analyse","données","rapport","étude","recherche"]):
        return ["research", "analytic", "summarizer"]
    elif any(w in t for w in ["stratégie","business","plan","marché","produit"]):
        return ["business", "marketing", "planner"]
    elif any(w in t for w in ["traduction","traduis","translate"]):
        return ["translator"]
    elif any(w in t for w in ["résumé","synthèse","condense"]):
        return ["summarizer"]
    elif any(w in t for w in ["debug","bug","erreur","corrige"]):
        return ["debugger", "reviewer"]
    else:
        return ["full", "reviewer"]


# ── Gestion d'erreur centralisée ─────────────────────────────────────────────

class CandyError(Exception):
    """Erreur candy avec contexte enrichi."""
    def __init__(self, message: str, context: dict = None):
        super().__init__(message)
        self.context = context or {}

    def __str__(self):
        base = super().__str__()
        if self.context:
            ctx = ", ".join(f"{k}={v}" for k, v in self.context.items())
            return f"{base} [{ctx}]"
        return base


# ── One-liner universel ───────────────────────────────────────────────────────

class _CandyUniversal:
    """
    Interface universelle v16.

    from candy import Candy
    print(Candy.ask("Explique les décorateurs Python"))
    print(Candy.score("fais un truc"))
    result = Candy.auto("Écris et vérifie un client REST")
    """

    def ask(self, prompt: str, lang: str = "FR",
            max_tokens: int = 1024, style: str = "default",
            temperature: float = 0.7, personality: Optional[str] = None,
            context: Optional[str] = None) -> str:
        p = get_profile("default")
        p2 = copy.copy(p)
        object.__setattr__(p2, "lang", lang)
        object.__setattr__(p2, "max_tokens", max_tokens)
        object.__setattr__(p2, "style", style)
        object.__setattr__(p2, "temperature", temperature)
        auto = personality or _detect_personality(prompt)
        result = _client.ask(auto, prompt, p2, context=context, stream=False)
        return result.get("response", "")

    def stream(self, prompt: str, lang: str = "FR",
               personality: Optional[str] = None):
        p = get_profile("default")
        p2 = copy.copy(p)
        object.__setattr__(p2, "lang", lang)
        auto = personality or _detect_personality(prompt)
        yield from _client.stream(auto, prompt, p2)

    def score(self, prompt: str) -> dict:
        """Score qualité d'un prompt (0-100, grade A→F)."""
        issues, suggestions = [], []
        score = 100

        if len(prompt) < 10:
            issues.append("Prompt trop court (<10 chars)")
            suggestions.append("Ajoute plus de contexte et de précision")
            score -= 40
        elif len(prompt) > 500:
            issues.append("Prompt trop long (>500 chars)")
            suggestions.append("Raccourcis à l'essentiel")
            score -= 20

        if len(prompt.split()) < 3:
            issues.append("Trop peu de mots (<3)")
            suggestions.append("Décris plus précisément ta demande")
            score -= 25

        if len(prompt) > 3 and prompt == prompt.upper():
            issues.append("Tout en majuscules")
            suggestions.append("Utilise une casse normale")
            score -= 10

        if not any(c in prompt for c in ["?", ".", "!", ":", ","]):
            suggestions.append("Ajoute une ponctuation pour clarifier l'intention")
            score -= 8

        vague = ["truc","chose","ça","ca","faire","un peu","peut-être","machin","bidule","genre","kek"]
        found_vague = [w for w in vague if w in prompt.lower().split()]
        if found_vague:
            issues.append(f"Termes vagues : {', '.join(found_vague)}")
            suggestions.append("Remplace par des termes précis et mesurables")
            score -= len(found_vague) * 15

        actions = ["écris","génère","explique","analyse","crée","résume","traduis","code",
                   "write","generate","create","explain","list","compare","describe","fix","review"]
        if not any(a in prompt.lower() for a in actions) and len(prompt.split()) > 2:
            suggestions.append("Commence par un verbe d'action clair")
            score -= 5

        score = max(0, min(100, score))
        detected = _detect_personality(prompt)
        return {
            "score":                score,
            "quality":              "excellent" if score >= 80 else "bon" if score >= 60 else "à améliorer" if score >= 40 else "insuffisant",
            "grade":                "A" if score >= 90 else "B" if score >= 75 else "C" if score >= 60 else "D" if score >= 40 else "F",
            "issues":               issues,
            "suggestions":          suggestions,
            "detected_personality": detected,
            "chars":                len(prompt),
            "words":                len(prompt.split()),
        }

    def fix(self, prompt: str, lang: str = "FR") -> str:
        """Auto-optimise un prompt."""
        p = get_profile("default")
        p2 = copy.copy(p)
        object.__setattr__(p2, "lang", lang)
        object.__setattr__(p2, "max_tokens", 300)
        result = _client.ask("prompt",
            f"Optimise ce prompt pour un LLM. Court, structuré, précis, avec verbe d'action. "
            f"Retourne UNIQUEMENT le prompt amélioré, rien d'autre : {prompt}",
            p2, stream=False)
        return result.get("response", prompt).strip()

    def dry_run(self, prompt: str, personality: Optional[str] = None) -> dict:
        """Simule sans appeler l'API."""
        detected   = personality or _detect_personality(prompt)
        score_data = self.score(prompt)
        return {
            "would_use_personality": detected,
            "prompt_score":          score_data["score"],
            "prompt_grade":          score_data["grade"],
            "prompt_quality":        score_data["quality"],
            "issues":                score_data["issues"],
            "suggestions":           score_data["suggestions"],
            "prompt_length":         len(prompt),
            "api_call":              "SKIPPED (dry-run mode)",
        }

    def auto(self, task: str, profile: str = "default",
             lang: str = "FR", verbose: bool = True) -> dict:
        """
        Auto-sélectionne la meilleure séquence multi-agents selon la tâche.
        Combine Candy.fix() + Agent.run() automatiquement.

        from candy import cfg, Candy
        cfg.default.lang = "FR"
        result = Candy.auto("Écris un script Python pour parser un CSV")
        print(result["final"])
        print(result["sequence_used"])
        """
        # 1. Optimise le prompt
        optimized = self.fix(task, lang=lang)
        if verbose:
            print(f"\n🍬 Candy.auto() — Analyse en cours...")
            print(f"  Prompt optimisé : {optimized[:80]}...")

        # 2. Détecte la séquence optimale
        sequence = _detect_best_sequence(task)
        if verbose:
            print(f"  Séquence détectée : {' → '.join(sequence)}")

        # 3. Importe dynamiquement les modules
        import importlib
        from . import modules as _mods
        module_objects = []
        for name in sequence:
            try:
                mod = importlib.import_module(f".modules.{name}", package="candy")
                cls_name = name.capitalize()
                if hasattr(mod, cls_name):
                    module_objects.append(getattr(mod, cls_name))
            except Exception:
                pass

        if not module_objects:
            # fallback
            from .modules.full import Full
            module_objects = [Full]

        # 4. Lance le pipeline
        agent = _Agent()
        if verbose:
            print(f"  Lancement pipeline ({len(module_objects)} étapes)...\n")
        result = agent.run(module_objects, optimized, profile=profile, verbose=verbose)
        result["sequence_used"]    = sequence
        result["original_task"]    = task
        result["optimized_prompt"] = optimized
        return result

    def chain(self, prompt: str, *module_names: str,
              profile: str = "default", lang: str = "FR") -> str:
        """
        Raccourci pipeline fluide en une ligne.

        from candy import Candy
        result = Candy.chain("Écris un article sur l'IA", "writing", "reviewer", "summarizer")
        print(result)
        """
        import importlib
        modules = []
        for name in module_names:
            try:
                mod = importlib.import_module(f".modules.{name}", package="candy")
                cls_name = name.capitalize()
                if hasattr(mod, cls_name):
                    modules.append(getattr(mod, cls_name))
            except Exception:
                pass
        if not modules:
            return self.ask(prompt, lang=lang)

        agent  = _Agent()
        result = agent.run(modules, prompt, profile=profile)
        return result["final"]

    def monitor(self, base_dir: Optional[str] = None) -> dict:
        """
        Stats en temps réel depuis les logs.
        Retourne insights optimisation IA.

        from candy import Candy
        stats = Candy.monitor()
        print(stats["top_personality"])
        print(stats["avg_prompt_score"])
        print(stats["recommendations"])
        """
        log_dir = Path(base_dir) if base_dir else Path.home() / ".candy" / "logs"
        if not log_dir.exists():
            return {"error": "Aucun log trouvé. Lance d'abord des requêtes avec Logger."}

        all_entries = []
        for log_file in log_dir.glob("*.json"):
            try:
                data = json.loads(log_file.read_text(encoding="utf-8"))
                all_entries.extend(data.get("entries", []))
            except Exception:
                pass

        if not all_entries:
            return {"error": "Logs vides."}

        # Calcul stats
        personalities = {}
        times         = []
        scores        = []
        errors        = 0

        for e in all_entries:
            p = e.get("personality", "unknown")
            personalities[p] = personalities.get(p, 0) + 1
            if e.get("time_ms"):
                times.append(e["time_ms"])
            if e.get("prompt_score"):
                scores.append(e["prompt_score"])
            if e.get("error"):
                errors += 1

        top_3 = sorted(personalities.items(), key=lambda x: x[1], reverse=True)[:3]
        avg_time  = round(sum(times) / len(times), 1) if times else 0
        avg_score = round(sum(scores) / len(scores), 1) if scores else 0

        # Recommandations auto
        recommendations = []
        if avg_score < 60:
            recommendations.append(f"Score moyen des prompts : {avg_score}/100. Utilise Candy.fix() pour améliorer.")
        if avg_time > 5000:
            recommendations.append(f"Temps moyen élevé ({avg_time}ms). Réduis max_tokens ou utilise style='concise'.")
        if errors > len(all_entries) * 0.1:
            recommendations.append(f"{errors} erreurs détectées. Vérifie cfg.p.retry = 2.")
        if not recommendations:
            recommendations.append("Tout est optimal ! Continue comme ça.")

        return {
            "total_requests":    len(all_entries),
            "top_personality":   top_3[0][0] if top_3 else "—",
            "top_3_personalities": dict(top_3),
            "avg_time_ms":       avg_time,
            "avg_prompt_score":  avg_score,
            "error_rate":        f"{round(errors/len(all_entries)*100, 1)}%",
            "recommendations":   recommendations,
        }

    def dashboard(self, base_dir: Optional[str] = None):
        """
        Affiche un dashboard ASCII dans le terminal.

        from candy import Candy
        Candy.dashboard()
        """
        stats = self.monitor(base_dir)
        if "error" in stats:
            print(f"⚠️  {stats['error']}")
            return

        w = 52
        print(f"\n{'─'*w}")
        print(f"  🍬 Candy Dashboard — {stats['total_requests']} requêtes")
        print(f"{'─'*w}")
        print(f"  Top personnalité  : {stats['top_personality']}")

        top3 = stats.get("top_3_personalities", {})
        for p, count in top3.items():
            bar = "█" * min(20, int(count / max(top3.values()) * 20))
            print(f"  {p:<16} {bar} {count}")

        print(f"{'─'*w}")
        print(f"  Temps moyen      : {stats['avg_time_ms']}ms")
        score = stats['avg_prompt_score']
        grade = "A" if score >= 90 else "B" if score >= 75 else "C" if score >= 60 else "D" if score >= 40 else "F"
        print(f"  Score prompts    : {score}/100  [{grade}]")
        print(f"  Taux d'erreur    : {stats['error_rate']}")
        print(f"{'─'*w}")
        print("  Recommandations :")
        for r in stats["recommendations"]:
            print(f"  → {r}")
        print(f"{'─'*w}\n")


Candy = _CandyUniversal()


# ── ask_validated ─────────────────────────────────────────────────────────────

def ask_validated(module, profile: str, prompt: str,
                  required_fields: list, max_attempts: int = 3) -> str:
    current_prompt = prompt
    for attempt in range(1, max_attempts + 1):
        response = module.use(profile).ask(current_prompt)
        if all(field in response for field in required_fields):
            return response
        missing = [f for f in required_fields if f not in response]
        current_prompt = (
            f"{prompt}\n\nTENTATIVE {attempt}/{max_attempts} — "
            f"CHAMPS MANQUANTS : {', '.join(missing)}\n"
            f"Respecte STRICTEMENT le format. Ne rien ajouter hors format."
        )
    return response


# ── ask_json ──────────────────────────────────────────────────────────────────

def ask_json(module, profile: str, prompt: str, max_attempts: int = 3) -> dict:
    current_prompt = prompt
    for attempt in range(1, max_attempts + 1):
        response = module.use(profile).ask(current_prompt)
        clean = re.sub(r"```json\s*", "", response.strip())
        clean = re.sub(r"```\s*", "", clean).strip()
        m = re.search(r'\{.*\}|\[.*\]', clean, re.DOTALL)
        if m:
            clean = m.group()
        try:
            return json.loads(clean)
        except json.JSONDecodeError as e:
            current_prompt = (
                f"JSON invalide (erreur: {e}).\nJSON reçu : {clean[:200]}\n\n"
                f"Retourne UNIQUEMENT un JSON valide, sans texte ni markdown.\n"
                f"Prompt original : {prompt}"
            )
    return {}


# ── Multi-agents ──────────────────────────────────────────────────────────────

class _Agent:

    def run(self, modules: list, task: str, profile: str = "default",
            instructions: Optional[list] = None, verbose: bool = False,
            dry_run: bool = False, visualize: bool = False) -> dict:
        """
        Pipeline séquentiel avec visualisation ASCII optionnelle.
        """
        if dry_run:
            return {
                "dry_run":     True,
                "steps":       {f"step_{i+1}": "SKIPPED" for i in range(len(modules))},
                "final":       "DRY RUN",
                "total_steps": len(modules),
            }

        steps   = {}
        current = task

        if visualize:
            names = [m.__class__.__name__ if not isinstance(m, type) else m.__name__ for m in modules]
            print(f"\n{'─'*50}")
            print(f"  🍬 Pipeline multi-agents ({len(modules)} étapes)")
            print(f"  {' → '.join(names)}")
            print(f"{'─'*50}")

        for i, module in enumerate(modules):
            name  = module.__class__.__name__ if not isinstance(module, type) else module.__name__
            instr = instructions[i] if instructions and i < len(instructions) else None
            prompt = f"{instr}\n\n{current}" if instr else current

            t0 = time.time()
            if verbose or visualize:
                print(f"  [{i+1}/{len(modules)}] {name}...", end="", flush=True)

            response = module.use(profile).ask(prompt)
            elapsed  = round((time.time() - t0) * 1000, 1)

            if verbose or visualize:
                words = len(response.split())
                print(f" ✓ {words} mots ({elapsed}ms)")

            steps[f"step_{i+1}_{name}"] = response
            current = response

        if visualize:
            print(f"{'─'*50}")
            print(f"  ✅ Pipeline terminé — {len(steps)} étapes")
            print(f"{'─'*50}\n")

        return {"steps": steps, "final": current, "total_steps": len(modules)}

    def debate(self, modules: list, question: str, profile: str = "default",
               visualize: bool = False) -> dict:
        from .modules.full import Full
        opinions = {}
        if visualize:
            names = [m.__class__.__name__ if not isinstance(m, type) else m.__name__ for m in modules]
            print(f"\n  🗣 Débat : {', '.join(names)}")
            print(f"  Question : {question[:60]}...")

        for module in modules:
            name = module.__class__.__name__ if not isinstance(module, type) else module.__name__
            if visualize:
                print(f"  [{name}] En train de répondre...", end="", flush=True)
            response = module.use(profile).ask(
                f"Donne ton point de vue d'expert en 100 mots max sur : {question}. "
                f"Sois direct et argumente clairement."
            )
            opinions[name] = response
            if visualize:
                print(" ✓")

        context   = "\n\n".join([f"{k}:\n{v}" for k, v in opinions.items()])
        if visualize:
            print("  [Full] Synthèse...", end="", flush=True)
        synthesis = Full.use(profile).ask(
            "Synthétise ces différents points de vue en une conclusion équilibrée de 150 mots.",
            context=context
        )
        if visualize:
            print(" ✓\n")

        return {"opinions": opinions, "synthesis": synthesis, "question": question}

    def build_app(self, description: str, profile: str = "default",
                  lang: str = "FR", output_dir: str = ".",
                  dry_run: bool = False) -> dict:
        if dry_run:
            return {"dry_run": True, "would_generate": ["main.py", "tests.py", "README.md"]}

        from .modules.coding import Coding
        from .modules.reviewer import Reviewer

        p = get_profile(profile)
        p2 = copy.copy(p)
        object.__setattr__(p2, "lang", lang)
        object.__setattr__(p2, "max_tokens", 4096)

        print("  [1/4] Code...", end="", flush=True)
        code = Coding.use(profile).ask(
            f"Code Python complet et fonctionnel pour : {description}. "
            f"Code seul, commenté, avec if __name__ == '__main__'.")
        print(" ✓")

        print("  [2/4] Tests...", end="", flush=True)
        tests = Coding.use(profile).ask("Génère les tests unitaires pytest.", context=code)
        print(" ✓")

        print("  [3/4] README...", end="", flush=True)
        readme = _client.ask("writing",
            f"README.md complet pour : {description}. Sections : Description, Installation, Usage.",
            p2, stream=False).get("response", "")
        print(" ✓")

        print("  [4/4] Review...", end="", flush=True)
        review = Reviewer.use(profile).ask("3 améliorations prioritaires.", context=code)
        print(" ✓")

        output = Path(output_dir)
        output.mkdir(exist_ok=True)
        files  = {"main.py": code, "tests.py": tests, "README.md": readme}
        for fname, content in files.items():
            (output / fname).write_text(content, encoding="utf-8")

        return {"files": list(files.keys()), "code": code, "tests": tests,
                "readme": readme, "review": review, "output_dir": str(output)}

    def pipeline(self, text: str, steps: list, lang: str = "FR",
                 visualize: bool = False) -> dict:
        current = text
        results = {}
        if visualize:
            print(f"\n  🔄 Pipeline {len(steps)} étapes :")
        for i, (module_name, instruction) in enumerate(steps):
            p = get_profile("default")
            p2 = copy.copy(p)
            object.__setattr__(p2, "lang", lang)
            object.__setattr__(p2, "max_tokens", 800)
            if visualize:
                print(f"  [{i+1}/{len(steps)}] {module_name}...", end="", flush=True)
            result = _client.ask(module_name,
                f"{instruction} Retourne UNIQUEMENT le résultat : {current}", p2, stream=False)
            current = result.get("response", current).strip()
            results[f"step_{i+1}_{module_name}"] = current
            if visualize:
                print(f" ✓ ({len(current.split())} mots)")
        return {"steps": results, "final": current}


Agent = _Agent()


# ── Mémoire persistante v16 ───────────────────────────────────────────────────

class Memory:
    """
    Sessions persistantes v16 avec collaboration multi-utilisateurs.

    from candy import Memory
    mem = Memory("mon_projet")
    mem.add("user", "Bonjour")
    mem.save()
    mem.share("mon_projet")   # crée un lien de partage
    """

    DEFAULT_DIR = Path.home() / ".candy" / "sessions"
    SHARED_DIR  = Path.home() / ".candy" / "shared"

    def __init__(self, session_name: str, base_dir: Optional[str] = None):
        self.session_name = session_name
        self.base_dir     = Path(base_dir) if base_dir else self.DEFAULT_DIR
        self.base_dir.mkdir(parents=True, exist_ok=True)
        self.path         = self.base_dir / f"{session_name}.json"
        self.history      = []
        self._version     = 1
        self._load()

    def _load(self):
        if self.path.exists():
            try:
                data = json.loads(self.path.read_text(encoding="utf-8"))
                self.history  = data.get("history", [])
                self._version = data.get("version", 1)
            except Exception:
                self.history = []

    def add(self, role: str, content: str, user: str = "default"):
        """Ajoute un message. user permet d'identifier qui écrit (collaboration)."""
        self.history.append({
            "role":      role,
            "content":   content,
            "user":      user,
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
        })

    def save(self):
        data = {
            "session":  self.session_name,
            "version":  self._version,
            "created":  self.history[0]["timestamp"] if self.history else "",
            "updated":  time.strftime("%Y-%m-%d %H:%M:%S"),
            "turns":    len([h for h in self.history if h["role"] == "user"]),
            "history":  self.history,
        }
        self.path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")

    def save_version(self, version_name: str) -> str:
        """Sauvegarde une version nommée (auto-versioning)."""
        vpath = self.base_dir / f"{self.session_name}_{version_name}.json"
        data  = {
            "session":      self.session_name,
            "version_name": version_name,
            "saved_at":     time.strftime("%Y-%m-%d %H:%M:%S"),
            "history":      self.history,
        }
        vpath.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
        return str(vpath)

    def share(self, alias: Optional[str] = None) -> str:
        """
        Partage la session pour collaboration multi-utilisateurs.
        Copie dans ~/.candy/shared/ accessible à tous les utilisateurs locaux.

        mem.share("projet_commun")
        # → Partagé dans ~/.candy/shared/projet_commun.json

        # Autre utilisateur :
        mem2 = Memory.from_shared("projet_commun")
        """
        self.SHARED_DIR.mkdir(parents=True, exist_ok=True)
        name  = alias or self.session_name
        spath = self.SHARED_DIR / f"{name}.json"
        data  = {
            "session":    self.session_name,
            "shared_as":  name,
            "shared_at":  time.strftime("%Y-%m-%d %H:%M:%S"),
            "shared_by":  os.getenv("USERNAME", os.getenv("USER", "unknown")),
            "history":    self.history,
        }
        spath.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
        return str(spath)

    @classmethod
    def from_shared(cls, shared_name: str) -> "Memory":
        """Charge une session partagée par un autre utilisateur."""
        spath = cls.SHARED_DIR / f"{shared_name}.json"
        if not spath.exists():
            raise CandyError(f"Session partagée '{shared_name}' introuvable",
                             {"path": str(spath)})
        data  = json.loads(spath.read_text(encoding="utf-8"))
        mem   = cls(data.get("session", shared_name))
        mem.history = data.get("history", [])
        return mem

    def sync_shared(self, shared_name: str):
        """Synchronise la session locale avec la version partagée."""
        shared = self.from_shared(shared_name)
        # Merge : ajoute les messages non présents
        existing_ts = {h["timestamp"] for h in self.history}
        for msg in shared.history:
            if msg["timestamp"] not in existing_ts:
                self.history.append(msg)
        self.history.sort(key=lambda x: x["timestamp"])

    def clear(self):
        self.history = []
        if self.path.exists():
            self.path.unlink()

    def as_context(self, last_n: int = 10) -> str:
        recent = self.history[-last_n:]
        return "\n".join([f"{h['role'].upper()}: {h['content']}" for h in recent])

    def count_turns(self) -> int:
        return len([h for h in self.history if h["role"] == "user"])

    def export(self, path: str):
        Path(path).write_text(
            json.dumps({"session": self.session_name, "history": self.history},
                       ensure_ascii=False, indent=2), encoding="utf-8")

    @classmethod
    def list_sessions(cls, base_dir: Optional[str] = None) -> list:
        d = Path(base_dir) if base_dir else cls.DEFAULT_DIR
        if not d.exists():
            return []
        return [f.stem for f in d.glob("*.json")]

    @classmethod
    def list_shared(cls) -> list:
        """Liste les sessions partagées disponibles."""
        if not cls.SHARED_DIR.exists():
            return []
        return [f.stem for f in cls.SHARED_DIR.glob("*.json")]

    @classmethod
    def delete_session(cls, session_name: str, base_dir: Optional[str] = None):
        d    = Path(base_dir) if base_dir else cls.DEFAULT_DIR
        path = d / f"{session_name}.json"
        if path.exists():
            path.unlink()


# ── Logger ────────────────────────────────────────────────────────────────────

class Logger:
    """Logs détaillés avec insights automatiques."""

    def __init__(self, name: str = "candy_log", base_dir: Optional[str] = None):
        self.name     = name
        self.base_dir = Path(base_dir) if base_dir else Path.home() / ".candy" / "logs"
        self.base_dir.mkdir(parents=True, exist_ok=True)
        self.path     = self.base_dir / f"{name}.json"
        self.entries  = []

    def log_request(self, personality: str, prompt: str, response: str,
                    time_ms: float = 0, error: Optional[str] = None):
        self.entries.append({
            "timestamp":    time.strftime("%Y-%m-%d %H:%M:%S"),
            "personality":  personality,
            "prompt":       prompt[:300],
            "response":     response[:500],
            "time_ms":      round(time_ms, 1),
            "error":        error,
            "words_out":    len(response.split()),
            "prompt_score": Candy.score(prompt)["score"],
        })

    def save(self):
        data = {"name": self.name, "entries": self.entries, "total": len(self.entries)}
        self.path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")

    def summary(self) -> dict:
        if not self.entries:
            return {"total": 0}
        personalities = {}
        for e in self.entries:
            personalities[e["personality"]] = personalities.get(e["personality"], 0) + 1
        avg_score = sum(e.get("prompt_score", 0) for e in self.entries) / len(self.entries)
        return {
            "total":              len(self.entries),
            "top_personality":    max(personalities, key=personalities.get),
            "personalities_used": personalities,
            "avg_time_ms":        round(sum(e["time_ms"] for e in self.entries) / len(self.entries), 1),
            "errors":             sum(1 for e in self.entries if e["error"]),
            "avg_prompt_score":   round(avg_score, 1),
        }
