from dataclasses import dataclass, asdict, field
import json
from typing import Optional

@dataclass
class DnoConfig:
    """
    The DNA Configuration for the Organism.
    Controls growth, survival, and resource constraints using deterministic numeric parameters.
    """
    # --- Control Mode ---
    manual_mode: bool = False
    initial_params_estimate: int = 50_000_000 # Target start for auto-scaling
    
    # --- Numeric Control Parameters ---
    # Growth
    growth_alpha: float = 0.5 # 0.0-1.0: Cloning speed/quantity coefficient
    entropy_threshold: float = 0.6 # Fixed float threshold for growth trigger
    growth_cooldown: int = 100 # Deprecated in favor of evolution_cooldown_steps
    evolution_cooldown_steps: int = 100 # Steps to wait between mitosis events
    min_loss_improvement: float = 0.001 # Required loss drop to allow new growth
    stabilization_steps: int = 100 # Steps for cold start decay
    
    # Survival
    pruning_decay_constant: float = 0.001 # Mathematical constant for decay speed
    utility_threshold: float = 0.01
    enable_negative_cleanup: bool = True # Master switch for weight cleaning
    grace_period: int = 100
    
    # --- Hardware Constraints ---
    vram_limit_gb: float = 4.0 # Physical growth limit
    max_param_count: int = 1_000_000_000 # Absolute cap
    
    # --- Architecture (Auto-Calculated if manual_mode=False) ---
    d_model: int = None
    n_heads: int = None
    learning_rate: float = None
    dropout_rate: float = 0.1
    
    # --- Runtime ---
    monologue_depth: int = 5

    def __post_init__(self):
        """Auto-scaling logic (Heuristics)."""
        if not self.manual_mode:
            # Heuristics for 50M parameter range (typical sizes)
            # This is a simplified scaling law implementation.
            
            if self.d_model is None:
                # 50M params -> approx d_model=512 is standard "Small" size
                if self.initial_params_estimate < 10_000_000:
                    self.d_model = 256
                elif self.initial_params_estimate < 100_000_000:
                    self.d_model = 512
                else:
                    self.d_model = 1024
            
            if self.n_heads is None:
                # d_model / 64 is a common head dimension
                self.n_heads = max(4, self.d_model // 64)
                
            if self.learning_rate is None:
                # Inverse sqrt scaling or fixed standard
                self.learning_rate = 1e-4 if self.d_model > 512 else 5e-4
                
    def save(self, path: str):
        with open(path, 'w') as f:
            json.dump(asdict(self), f, indent=4)
            
    @classmethod
    def load(cls, path: str):
        with open(path, 'r') as f:
            data = json.load(f)
        return cls(**data)
