"""Property-based tests for classification closure in the Ink language."""

from hypothesis import given, strategies as st
from ink.ast_nodes import ClassificationEdge
from ink.interpreter import compute_classification_closure


# Generators for test data
def identifiers():
    """Generate valid identifiers (non-variable)."""
    return st.text(min_size=1, max_size=10, alphabet=st.characters(
        whitelist_categories=('Lu', 'Ll', 'Lo'),
        blacklist_characters='其'
    ))


def classification_edges_concrete():
    """Generate concrete classification edges (no variables)."""
    return st.builds(
        ClassificationEdge,
        identifiers(),
        st.just(False),
        identifiers(),
        st.just(False)
    )


# Feature: ink-language-mvp, Property 34: Transitive closure derivation
@given(identifiers(), identifiers(), identifiers())
def test_transitive_closure_derivation(a, b, c):
    """For any classification edges A⊑B and B⊑C, the closure should include A⊑C."""
    # Skip if identifiers are not distinct enough to form a proper chain
    if a == b or b == c or a == c:
        return
    
    # Create edges A⊑B and B⊑C
    edge_ab = ClassificationEdge(a, False, b, False)
    edge_bc = ClassificationEdge(b, False, c, False)
    
    # Compute closure
    edges = {edge_ab, edge_bc}
    closure = compute_classification_closure(edges)
    
    # The transitive edge A⊑C should be in the closure
    edge_ac = ClassificationEdge(a, False, c, False)
    assert edge_ac in closure
    
    # Original edges should also be in the closure
    assert edge_ab in closure
    assert edge_bc in closure


# Feature: ink-language-mvp, Property 35: Closure deduplication
@given(st.sets(classification_edges_concrete(), min_size=1, max_size=10))
def test_closure_deduplication(edges):
    """For any set of classification edges, computing closure should not produce duplicates."""
    closure = compute_classification_closure(edges)
    
    # Convert to list to check for duplicates
    closure_list = list(closure)
    
    # No duplicates means length of set equals length of list
    assert len(closure) == len(closure_list)
    
    # All original edges should be in the closure
    for edge in edges:
        assert edge in closure
