from abc import ABC, abstractmethod
import warnings
from dataclasses import dataclass, field
from typing import Callable

from .am_symbol import Symbol
from .am_machine import Machine
from .am_cohesion import CohesionKernel

TNorm = Callable[[float, float], float]

PRODUCT     : TNorm = lambda a, b: a * b
MINIMUM     : TNorm = lambda a, b: min(a, b)
LUKASIEWICZ : TNorm = lambda a, b: max(0.0, a + b - 1.0)

class SyntagmaticRule(ABC):
	@abstractmethod
	def applies(self, domain ) -> bool:...
	def cohesion_scores(self, symbol : Symbol) -> dict[str, float]:...

@dataclass
class ExplicitCohesionRule(SyntagmaticRule):

	machine : Machine
	domains  : set[str]
	kernel : CohesionKernel
	
	def applies(self,domain) :
		return domain in self.domains

	def cohesion_score(self, source : Symbol, symbols : set[Symbol] ) -> dict[str, float]:
		return self.kernel.cohesion_scores( source, symbols )

@dataclass
class Syntagmatics:
	"""
	Defines the syntagmatic structure encompassing selection,
	cohesion, variability, and scope constraints.

	Also may be considered to define a context (e.g., the model topology) dependent stochastic groupoid.
	"""
	rules : list[SyntagmaticRule] = field(default_factory=list)
	t_norm : TNorm = field(default=PRODUCT)

	def _combine_cohesion_scores(self, source: str, domain: set[str]) -> dict[str, float]:
		active  = [r for r in self.rules if r.applies(domain)]
		symbols = set.intersection(*(set(r.symbols) for r in active))
		scores  = {s: 1.0 for s in symbols}
		for rule in active:
			for s, w in rule.cohesion_scores(source, symbols).items():
				scores[s] = self.t_norm(scores[s], w)
		return scores

	def selection_probabilities(self, source: str, domain: set[str]) -> dict[str, float]:
		return self._normalize(source, self._combine_cohesion_scores(source, domain))
