"""Embedding-based strategy similarity detection — prevents duplicate generation.
Uses lightweight TF-IDF cosine similarity (no GPU/API needed)."""

from __future__ import annotations
import math, re
from collections import Counter
from typing import Optional

def _tokenize(code: str) -> list[str]:
    """Simple tokenizer for Python strategy code."""
    return re.findall(r'[a-zA-Z_]\w*', code.lower())

def _tfidf_vector(tokens: list[str], global_df: dict[str, int], total_docs: int) -> dict[str, float]:
    tf = Counter(tokens)
    vec = {}
    for term, freq in tf.items():
        df = global_df.get(term, 1)
        vec[term] = (freq / max(len(tokens), 1)) * math.log((total_docs + 1) / (df + 1)) + 1
    return vec

def cosine_similarity(a: dict[str, float], b: dict[str, float]) -> float:
    all_terms = set(a) | set(b)
    dot = sum(a.get(t, 0) * b.get(t, 0) for t in all_terms)
    norm_a = math.sqrt(sum(v**2 for v in a.values())) or 1
    norm_b = math.sqrt(sum(v**2 for v in b.values())) or 1
    return dot / (norm_a * norm_b)

class StrategySimilarity:
    """Detect if a new strategy is too similar to existing ones."""

    def __init__(self):
        self._docs: dict[str, str] = {}  # strategy_id → source code
        self._global_df: dict[str, int] = Counter()

    def index_strategy(self, strategy_id: str, source_code: str) -> None:
        tokens = _tokenize(source_code)
        self._docs[strategy_id] = source_code
        for term in set(tokens):
            self._global_df[term] += 1

    def check_similarity(self, new_code: str, threshold: float = 0.75) -> dict:
        """Returns {max_similarity, most_similar_id, is_duplicate}."""
        if not self._docs: return {"max_similarity": 0.0, "most_similar_id": None, "is_duplicate": False}
        new_tokens = _tokenize(new_code)
        new_vec = _tfidf_vector(new_tokens, self._global_df, len(self._docs) + 1)
        best_id, best_score = None, 0.0
        for sid, code in self._docs.items():
            existing_vec = _tfidf_vector(_tokenize(code), self._global_df, len(self._docs) + 1)
            score = cosine_similarity(new_vec, existing_vec)
            if score > best_score:
                best_score, best_id = score, sid
        return {"max_similarity": round(best_score, 4), "most_similar_id": best_id,
                "is_duplicate": best_score >= threshold}

_sim_checker: Optional[StrategySimilarity] = None
def get_similarity_checker() -> StrategySimilarity:
    global _sim_checker
    if _sim_checker is None: _sim_checker = StrategySimilarity()
    return _sim_checker
