from typing import List, Dict, Any
import re

class SwarmConsensus:
    """Core logic for Multi-Swarm Consensus Algorithms (V38)."""
    
    def __init__(self):
        pass

    def calculate_heuristics(self, content: str, keywords: List[str]) -> Dict[str, float]:
        """Calculates basic heuristics for a given proposal content."""
        length_score = float(len(content))
        
        keyword_matches = 0
        content_lower = content.lower()
        for kw in keywords:
            # Simple word boundary regex match for keywords
            matches = re.findall(rf'\b{re.escape(kw.lower())}\b', content_lower)
            keyword_matches += len(matches)
            
        return {
            "length": length_score,
            "keyword_match": float(keyword_matches)
        }

    def conduct_vote(
        self, 
        proposals: List[Dict[str, Any]], 
        scoring_weights: Dict[str, float], 
        keywords: List[str] = None
    ) -> Dict[str, Any]:
        """
        Evaluates solutions based on criteria (heuristics) rather than random choice.
        
        Args:
            proposals: A list of dicts, e.g. [{"id": "p1", "content": "..."}]
            scoring_weights: Weights applied to metrics, e.g. {"length": 0.5, "keyword_match": 1.5}
            keywords: Optional list of keywords for the 'keyword_match' metric.
            
        Returns:
            The winning proposal dict with a new 'final_score' key.
        """
        if not proposals:
            return None
            
        if keywords is None:
            keywords = []
            
        best_proposal = None
        best_score = -float('inf')
        
        for proposal in proposals:
            content = proposal.get("content", "")
            
            # Start with pre-calculated metrics if any exist
            metrics = proposal.get("metrics", {})
            calculated_metrics = self.calculate_heuristics(content, keywords)
            
            # Merge metrics (pre-calculated ones take precedence)
            final_metrics = {**calculated_metrics, **metrics}
            
            score = 0.0
            for metric, weight in scoring_weights.items():
                score += final_metrics.get(metric, 0.0) * weight
                
            if score > best_score:
                best_score = score
                best_proposal = proposal
                
        # Return a copy to avoid mutating the original
        if best_proposal:
            result = dict(best_proposal)
            result["final_score"] = best_score
            return result
            
        return None
