"""kOREInference — Algorithm 4: iDRegEx (arXiv 1004.2372)."""

from .ikoa import ikoa
from .rwrsq import rwr_sq
from .idregex import is_deterministic
from .mdl import mdl_score
from .grammar import Epsilon, Empty, Symbol, Plus, Optional, Star, Concat, Alt, alphabet as ast_alphabet


def validate_k_ore(node, k, alphabet_set=None):
    """Check if a k-ORE satisfies the k-occurrence condition."""
    if node is None or isinstance(node, (Empty, Epsilon)):
        return True, "OK"
    syms = alphabet_set or ast_alphabet(node)
    counts = _count_symbol_occurrences(node)
    violations = [f"{s}:{c}" for s, c in sorted(counts.items()) if c > k]
    if violations:
        return False, f"k={k} violations: {', '.join(violations)}"
    return True, "OK"


def _count_symbol_occurrences(node):
    """Count how many times each symbol appears as a leaf in the AST."""
    if isinstance(node, Symbol):
        return {node.value: 1}
    if isinstance(node, (Epsilon, Empty)):
        return {}
    if isinstance(node, (Plus, Optional, Star)):
        return _count_symbol_occurrences(node.child)
    if isinstance(node, (Concat, Alt)):
        counts = {}
        for p in node.parts:
            for sym, cnt in _count_symbol_occurrences(p).items():
                counts[sym] = counts.get(sym, 0) + cnt
        return counts
    return {}


def _count_nodes(node):
    if isinstance(node, Symbol):
        return 1
    if isinstance(node, (Epsilon, Empty)):
        return 1
    if isinstance(node, (Plus, Optional, Star)):
        return 1 + _count_nodes(node.child)
    if isinstance(node, (Concat, Alt)):
        return 1 + sum(_count_nodes(p) for p in node.parts)
    return 1


class kOREInference:
    def __init__(self, k_max=5, N=5):
        self.k_max = k_max
        self.N = N

    def infer(self, sequences):
        sequences = [s for s in sequences if s]
        if not sequences:
            return None
        candidates = []
        for k in range(1, self.k_max + 1):
            for _ in range(self.N):
                G = ikoa(sequences, k, num_trials=1)
                if G is None:
                    continue
                expr = rwr_sq(G)
                if expr is not None and not isinstance(expr, (Empty, Epsilon)):
                    if is_deterministic(expr):
                        valid, _ = validate_k_ore(expr, k)
                        if valid:
                            candidates.append((G, expr, k))
        if not candidates:
            return None
        return min(candidates, key=lambda c: mdl_score(c[1], sequences))
