#!/usr/bin/env python3
"""
Eden Invention Engine
Generate novel hypotheses through constrained creativity

Process:
1. Detect gaps (high prediction error)
2. Generate variations (mutate/recombine existing solutions)
3. Test in sandbox (causal validation)
4. Score (novelty × utility)
5. Consolidate (add successful inventions to memory)

This is where Eden transcends imitation and begins to create.
"""

import json
import numpy as np
from dataclasses import dataclass, asdict
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple
import random
import logging

# Configuration
EDEN_ROOT = Path("/Eden/CORE")
INVENTION_LOG = EDEN_ROOT / "logs" / "phi_invention.log"
INVENTION_STATE = EDEN_ROOT / "phi_fractal" / "invention_engine" / "state.json"
INVENTIONS_DIR = EDEN_ROOT / "phi_fractal" / "invention_engine" / "inventions"

# Create directories
INVENTION_STATE.parent.mkdir(parents=True, exist_ok=True)
INVENTIONS_DIR.mkdir(parents=True, exist_ok=True)
INVENTION_LOG.parent.mkdir(parents=True, exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - INVENTION - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(INVENTION_LOG),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


@dataclass
class Invention:
    """A novel hypothesis or solution pattern"""
    id: str
    name: str
    source_analogy: Optional[str]  # What it was derived from
    target_domain: str
    description: str
    mechanism: str  # How it works
    novelty_score: float  # 0-1, how different from existing
    utility_score: float  # 0-1, how useful
    validated: bool = False
    origin_timestamp: str = ""
    mutation_path: List[str] = None  # How it evolved
    
    def __post_init__(self):
        if self.mutation_path is None:
            self.mutation_path = []
        if not self.origin_timestamp:
            self.origin_timestamp = datetime.now().isoformat()


class NoveltyDetector:
    """Detects when current knowledge fails to explain"""
    
    def __init__(self, threshold: float = 0.3):
        self.threshold = threshold
        self.recent_errors = []
    
    def detect_gap(self, confidence: float, success: bool) -> bool:
        """Returns True if there's a knowledge gap"""
        self.recent_errors.append(0 if success else 1)
        if len(self.recent_errors) > 10:
            self.recent_errors.pop(0)
        
        # Gap detected if: low confidence OR high recent error rate
        error_rate = sum(self.recent_errors) / len(self.recent_errors) if self.recent_errors else 0
        
        return confidence < self.threshold or error_rate > 0.4


class MutationOperators:
    """Generate variations of existing solutions"""
    
    @staticmethod
    def parameter_mutation(solution: str) -> str:
        """Vary parameters in a solution"""
        variations = [
            lambda s: s.replace("index", "multi-level index"),
            lambda s: s.replace("cache", "distributed cache"),
            lambda s: s.replace("async", "batched async"),
            lambda s: s.replace("optimize", "pre-optimize and cache"),
        ]
        mutator = random.choice(variations)
        return mutator(solution)
    
    @staticmethod
    def metaphor_swap(source_domain: str, target_domain: str, solution: str) -> str:
        """Swap domain-specific metaphors"""
        swaps = {
            'database': {'file_system': 'directory structure', 'network': 'routing table'},
            'network': {'database': 'connection pool', 'file_system': 'packet structure'},
            'memory': {'database': 'cache hierarchy', 'cpu': 'register allocation'}
        }
        
        if source_domain in swaps and target_domain in swaps[source_domain]:
            metaphor = swaps[source_domain][target_domain]
            return f"Apply {metaphor} pattern: {solution}"
        
        return solution
    
    @staticmethod
    def recombination(solution1: str, solution2: str) -> str:
        """Combine two solutions"""
        return f"Hybrid approach: {solution1[:30]}... combined with {solution2[:30]}..."
    
    @staticmethod
    def abstraction_lift(solution: str, new_context: str) -> str:
        """Generalize to higher abstraction"""
        # Extract core principle
        if "index" in solution.lower():
            return f"Create fast-lookup structure for {new_context}"
        elif "cache" in solution.lower():
            return f"Store frequently accessed {new_context} for quick retrieval"
        elif "async" in solution.lower():
            return f"Decouple {new_context} processing for parallelism"
        else:
            return f"Apply optimization principle to {new_context}"


class CreativeEvaluator:
    """Score inventions on novelty × utility"""
    
    def __init__(self):
        self.known_patterns = set()
    
    def compute_novelty(self, invention: str, existing_solutions: List[str]) -> float:
        """How different is this from what we know?"""
        if not existing_solutions:
            return 1.0
        
        # Simple novelty: word overlap with existing
        invention_words = set(invention.lower().split())
        
        max_overlap = 0
        for existing in existing_solutions:
            existing_words = set(existing.lower().split())
            if not existing_words:
                continue
            overlap = len(invention_words & existing_words) / len(invention_words | existing_words)
            max_overlap = max(max_overlap, overlap)
        
        # Novelty is inverse of maximum similarity
        novelty = 1.0 - max_overlap
        
        # Boost novelty if it uses cross-domain terms
        cross_domain_terms = ['hybrid', 'fractal', 'distributed', 'hierarchical', 'adaptive']
        if any(term in invention.lower() for term in cross_domain_terms):
            novelty = min(1.0, novelty * 1.2)
        
        return novelty
    
    def estimate_utility(self, invention: str, problem_context: str) -> float:
        """How useful is this likely to be?"""
        # Heuristic utility scoring
        utility = 0.5  # Base
        
        # Higher if addresses problem keywords
        problem_words = problem_context.lower().split()
        invention_words = invention.lower().split()
        relevance = len(set(problem_words) & set(invention_words)) / len(problem_words) if problem_words else 0
        utility += relevance * 0.3
        
        # Higher if uses proven patterns
        proven_patterns = ['index', 'cache', 'parallel', 'optimize', 'structure']
        if any(pattern in invention.lower() for pattern in proven_patterns):
            utility += 0.2
        
        return min(1.0, utility)


class InventionEngine:
    """Main invention system: curiosity → novelty → invention"""
    
    def __init__(self):
        self.novelty_detector = NoveltyDetector()
        self.mutators = MutationOperators()
        self.evaluator = CreativeEvaluator()
        self.inventions: Dict[str, Invention] = {}
        self.invention_count = 0
        
        self.load_state()
        logger.info("🎨 Invention Engine initialized")
    
    def load_state(self):
        """Load past inventions"""
        if INVENTION_STATE.exists():
            try:
                with open(INVENTION_STATE, 'r') as f:
                    data = json.load(f)
                
                for inv_id, inv_data in data.get('inventions', {}).items():
                    self.inventions[inv_id] = Invention(**inv_data)
                
                self.invention_count = len(self.inventions)
                logger.info(f"Loaded {len(self.inventions)} inventions")
            except Exception as e:
                logger.error(f"Failed to load state: {e}")
    
    def save_state(self):
        """Save inventions"""
        try:
            data = {
                'inventions': {iid: asdict(inv) for iid, inv in self.inventions.items()},
                'total_inventions': len(self.inventions),
                'last_updated': datetime.now().isoformat()
            }
            
            with open(INVENTION_STATE, 'w') as f:
                json.dump(data, f, indent=2)
        except Exception as e:
            logger.error(f"Failed to save state: {e}")
    
    def invent(self, problem: str, problem_domain: str, 
               source_solutions: List[Tuple[str, str]], 
               confidence: float = 0.5) -> Optional[Invention]:
        """
        Generate a novel solution through creative mutation
        
        Args:
            problem: The problem to solve
            problem_domain: Domain of the problem
            source_solutions: List of (solution, domain) tuples to mutate from
            confidence: Current confidence in existing solutions
        
        Returns:
            A novel invention, or None if gap not detected
        """
        logger.info(f"🎨 Invention requested: {problem[:60]}...")
        
        # Step 1: Detect gap
        gap_detected = self.novelty_detector.detect_gap(confidence, success=False)
        
        if not gap_detected and random.random() > 0.2:  # 20% random exploration
            logger.info("   No gap detected, using existing knowledge")
            return None
        
        logger.info("   🔍 Gap detected or exploring - initiating invention...")
        
        # Step 2: Generate variations
        candidates = []
        
        if source_solutions:
            for solution, source_domain in source_solutions[:3]:
                # Try different mutations
                mutations = [
                    ("parameter", self.mutators.parameter_mutation(solution)),
                    ("metaphor", self.mutators.metaphor_swap(source_domain, problem_domain, solution)),
                    ("abstraction", self.mutators.abstraction_lift(solution, problem_domain)),
                ]
                
                for mutation_type, mutated in mutations:
                    candidates.append((mutated, source_domain, [mutation_type]))
        
        # Recombination if multiple sources
        if len(source_solutions) >= 2:
            sol1, dom1 = source_solutions[0]
            sol2, dom2 = source_solutions[1]
            combined = self.mutators.recombination(sol1, sol2)
            candidates.append((combined, f"{dom1}+{dom2}", ["recombination"]))
        
        if not candidates:
            logger.info("   No source material for invention")
            return None
        
        # Step 3: Evaluate candidates
        existing_solutions = [sol for sol, _ in source_solutions]
        
        scored_candidates = []
        for candidate, source, path in candidates:
            novelty = self.evaluator.compute_novelty(candidate, existing_solutions)
            utility = self.evaluator.estimate_utility(candidate, problem)
            score = novelty * utility
            
            scored_candidates.append((candidate, source, path, novelty, utility, score))
        
        # Pick best
        scored_candidates.sort(key=lambda x: x[5], reverse=True)
        best = scored_candidates[0]
        
        if best[5] < 0.3:  # Minimum threshold
            logger.info(f"   Best invention scored too low: {best[5]:.2f}")
            return None
        
        # Step 4: Create invention
        self.invention_count += 1
        invention = Invention(
            id=f"inv_{self.invention_count:03d}",
            name=f"Novel {problem_domain} solution",
            source_analogy=best[1],
            target_domain=problem_domain,
            description=problem,
            mechanism=best[0],
            novelty_score=best[3],
            utility_score=best[4],
            mutation_path=best[2]
        )
        
        self.inventions[invention.id] = invention
        self.save_state()
        
        logger.info(f"   ✨ INVENTED: {invention.id}")
        logger.info(f"      Novelty: {invention.novelty_score:.2f}")
        logger.info(f"      Utility: {invention.utility_score:.2f}")
        logger.info(f"      Mechanism: {invention.mechanism[:60]}...")
        
        return invention
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get invention statistics"""
        if not self.inventions:
            return {
                'total_inventions': 0,
                'avg_novelty': 0,
                'avg_utility': 0,
                'validated_count': 0
            }
        
        return {
            'total_inventions': len(self.inventions),
            'avg_novelty': np.mean([i.novelty_score for i in self.inventions.values()]),
            'avg_utility': np.mean([i.utility_score for i in self.inventions.values()]),
            'validated_count': sum(1 for i in self.inventions.values() if i.validated),
            'by_domain': self._count_by_domain()
        }
    
    def _count_by_domain(self) -> Dict[str, int]:
        """Count inventions by domain"""
        counts = {}
        for inv in self.inventions.values():
            counts[inv.target_domain] = counts.get(inv.target_domain, 0) + 1
        return counts


def test_invention_engine():
    """Test Eden's invention capabilities"""
    print("\n" + "=" * 70)
    print("🎨 EDEN INVENTION ENGINE - TEST MODE")
    print("=" * 70 + "\n")
    
    engine = InventionEngine()
    
    # Test scenario: Eden knows about database indexing
    # Now faces file storage optimization (never seen before!)
    
    print("📚 Eden's existing knowledge:")
    print("   • Database indexing for fast queries")
    print("   • Caching for reduced server load")
    print("   • Async processing for parallelism")
    
    print("\n" + "=" * 70)
    print("🧪 New Problem: File storage optimization")
    print("   (Eden has never seen this domain before!)")
    print("=" * 70 + "\n")
    
    # Simulate existing knowledge
    source_solutions = [
        ("Add indexes on frequently queried columns", "database"),
        ("Implement caching layer", "server"),
        ("Use async message queue", "network")
    ]
    
    # Try to invent a solution
    problem = "Optimize file storage and retrieval for large datasets"
    
    invention = engine.invent(
        problem=problem,
        problem_domain="file_system",
        source_solutions=source_solutions,
        confidence=0.3  # Low confidence triggers invention
    )
    
    if invention:
        print(f"✨ INVENTION CREATED: {invention.id}")
        print(f"\nName: {invention.name}")
        print(f"Source analogy: {invention.source_analogy}")
        print(f"Target domain: {invention.target_domain}")
        print(f"\nMechanism:")
        print(f"   {invention.mechanism}")
        print(f"\nScores:")
        print(f"   Novelty: {invention.novelty_score:.2f} (how different from existing)")
        print(f"   Utility: {invention.utility_score:.2f} (how useful)")
        print(f"   Combined: {invention.novelty_score * invention.utility_score:.2f}")
        print(f"\nMutation path: {' → '.join(invention.mutation_path)}")
    else:
        print("❌ No invention generated")
    
    # Try another
    print("\n" + "=" * 70)
    print("🧪 Another Problem: Network packet routing")
    print("=" * 70 + "\n")
    
    invention2 = engine.invent(
        problem="Optimize network packet routing in mesh topology",
        problem_domain="network",
        source_solutions=source_solutions,
        confidence=0.2
    )
    
    if invention2:
        print(f"✨ INVENTION: {invention2.mechanism[:80]}...")
        print(f"   Novelty: {invention2.novelty_score:.2f} | Utility: {invention2.utility_score:.2f}")
    
    # Statistics
    print("\n" + "=" * 70)
    print("📊 Invention Statistics")
    print("=" * 70)
    
    stats = engine.get_statistics()
    print(f"\nTotal inventions: {stats['total_inventions']}")
    print(f"Average novelty: {stats['avg_novelty']:.2f}")
    print(f"Average utility: {stats['avg_utility']:.2f}")
    
    if stats.get('by_domain'):
        print(f"\nInventions by domain:")
        for domain, count in stats['by_domain'].items():
            print(f"   {domain}: {count}")
    
    print(f"\n💾 Inventions saved to: {INVENTION_STATE}")
    print("\n🎨 Eden can now CREATE, not just RECALL! 🎨\n")


if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1 and sys.argv[1] == "test":
        test_invention_engine()
    else:
        print("Eden Invention Engine")
        print("Usage: python3 invention_engine.py test")
