from __future__ import annotations

from typing import Dict, List, Set

from ..model import Attribute, DiagramElement, Entity, Mechanic
from ..model.sheep_wolf_diagram import SHEEP_WOLF_DIAGRAM
from .preprocess import preprocess_diagram


def _find_loops_from_mechanic(
    start_mechanic: Mechanic,
    current: DiagramElement,
    path: List[str],
    visited: Set[str],
    loops: List[List[str]]
) -> None:
    if current.id == start_mechanic.id and len(path) > 0:
        loops.append(path + [current.id])
        return
    
    if current.id in visited:
        return
    
    visited.add(current.id)
    
    for link in getattr(current, "links", []):
        target = getattr(link, "target", None)
        if target is None:
            continue
        
        _find_loops_from_mechanic(start_mechanic, target, path + [current.id], visited.copy(), loops)


def _is_subloop(shorter: List[str], longer: List[str]) -> bool:
    if len(shorter) >= len(longer):
        return False
    
    shorter_cycle = shorter[:-1]
    longer_cycle = longer[:-1]
    
    if len(shorter_cycle) == 0:
        return False
    
    for i in range(len(longer_cycle)):
        match = True
        for j in range(len(shorter_cycle)):
            if longer_cycle[(i + j) % len(longer_cycle)] != shorter_cycle[j]:
                match = False
                break
        if match:
            return True
    
    return False


def _filter_subloops(loops: List[List[str]]) -> List[List[str]]:
    filtered = []
    sorted_loops = sorted(loops, key=len, reverse=True)
    
    for i, loop in enumerate(sorted_loops):
        is_sub = False
        for j in range(i):
            if _is_subloop(loop, sorted_loops[j]):
                is_sub = True
                break
        if not is_sub:
            filtered.append(loop)
    
    return filtered


def calculate_feedback_loops() -> Dict[str, any]:
    diagram = preprocess_diagram(SHEEP_WOLF_DIAGRAM)
    
    all_loops = []
    
    for node in diagram.nodes.values():
        if not isinstance(node, Mechanic):
            continue
        
        loops: List[List[str]] = []
        
        for link in getattr(node, "links", []):
            target = getattr(link, "target", None)
            if target is None:
                continue
            
            visited: Set[str] = set()
            _find_loops_from_mechanic(node, target, [node.id], visited, loops)
        
        all_loops.extend(loops)
    
    filtered_loops = _filter_subloops(all_loops)
    
    # Filter out loops with 3 or fewer nodes
    filtered_loops = [loop for loop in filtered_loops if len(loop) > 3]
    
    # Find longest loop length
    longest_loop_length = max(len(loop) for loop in filtered_loops) if filtered_loops else 0
    
    # Count loops per target
    target_loop_counts: Dict[str, int] = {}
    for loop in filtered_loops:
        targets_in_loop = set()
        for node_id in loop:
            loop_node = diagram.nodes.get(node_id)
            if isinstance(loop_node, Entity) and loop_node.is_target:
                targets_in_loop.add(node_id)
            elif isinstance(loop_node, Attribute):
                parent = getattr(loop_node, "parent", None)
                if parent and getattr(parent, "is_target", False):
                    targets_in_loop.add(parent.id)
        
        for target_id in targets_in_loop:
            target_loop_counts[target_id] = target_loop_counts.get(target_id, 0) + 1
    
    return {
        "total_loops": len(filtered_loops),
        "longest_loop_length": longest_loop_length,
        "targets_loop_count": target_loop_counts,
        "all_loops": filtered_loops
    }


if __name__ == "__main__":
    import json
    results = calculate_feedback_loops()
    print(json.dumps(results, indent=2))

