"""
Canvas Utilities
Helper functions for canvas operations and node management.
"""

from typing import List, Dict, Any, Tuple, Optional
import uuid
import logging

logger = logging.getLogger(__name__)

# ==================== Position Calculation ====================

def calculate_grid_positions(
    count: int,
    start_x: int = 100,
    start_y: int = 100,
    spacing_x: int = 300,
    spacing_y: int = 200,
    columns: int = 3
) -> List[Dict[str, int]]:
    """
    Calculate grid positions for multiple nodes.
    
    Args:
        count: Number of positions to calculate
        start_x: Starting X coordinate
        start_y: Starting Y coordinate
        spacing_x: Horizontal spacing between nodes
        spacing_y: Vertical spacing between nodes
        columns: Number of columns in grid
    
    Returns:
        List of position dictionaries with x and y coordinates
    """
    positions = []
    for i in range(count):
        row = i // columns
        col = i % columns
        positions.append({
            "x": start_x + col * spacing_x,
            "y": start_y + row * spacing_y
        })
    return positions


def calculate_horizontal_positions(
    count: int,
    start_x: int = 100,
    start_y: int = 100,
    spacing: int = 300
) -> List[Dict[str, int]]:
    """Calculate horizontal line positions.""" 
    return [
        {"x": start_x + i * spacing, "y": start_y}
        for i in range(count)
    ]


def calculate_vertical_positions(
    count: int,
    start_x: int = 100,
    start_y: int = 100,
    spacing: int = 200
) -> List[Dict[str, int]]:
    """Calculate vertical line positions.""" 
    return [
        {"x": start_x, "y": start_y + i * spacing}
        for i in range(count)
    ]


def calculate_circular_positions(
    count: int,
    center_x: int = 500,
    center_y: int = 300,
    radius: int = 250
) -> List[Dict[str, int]]:
    """
    Calculate circular positions around a center point.
    Useful for hub-and-spoke layouts.
    """ 
    import math
    positions = []
    for i in range(count):
        angle = (2 * math.pi * i) / count
        x = center_x + int(radius * math.cos(angle))
        y = center_y + int(radius * math.sin(angle))
        positions.append({"x": x, "y": y})
    return positions


# ==================== Node Generation ====================

def generate_node_id(prefix: str = "node") -> str:
    """ Generate a unique node ID.""" 
    return f"{prefix}_{uuid.uuid4().hex[:8]}"


def create_node_config(
    node_type: str,
    title: str,
    position: Dict[str, int],
    **kwargs
) -> Dict[str, Any]:
    """ Create a standardized node configuration.
    
    Args:
        node_type: Type of node (IMAGE_GENERATOR, VIDEO_GENERATOR, etc.)
        title: Node title
        position: Position dictionary with x and y
        **kwargs: Additional node properties
    
    Returns:
        Complete node configuration dictionary
    """ 
    node_id = kwargs.pop("id", generate_node_id())
    
    config = {
        "id": node_id,
        "type": node_type,
        "title": title,
        "position": position,
        "status": "IDLE"
    }
    
    # Add additional properties
    config.update(kwargs)
    
    return config


def create_edge_config(
    source: str,
    target: str,
    edge_id: Optional[str] = None
) -> Dict[str, str]:
    """ 创建 a standardized edge configuration.
    
    Args:
        source: Source node ID
        target: Target node ID
        edge_id: Optional custom edge ID
    
    Returns:
        Edge configuration dictionary
    """
    if not edge_id:
        edge_id = f"edge_{source}_to_{target}"
    
    return {
        "id": edge_id,
        "source": source,
        "target": target
    }


# ==================== Workflow Builders ====================

class WorkflowBuilder:
    """ 构建器 class for creating complex workflows."""
    
    def __init__(self, start_x: int = 100, start_y: int = 100):
        self.nodes: List[Dict[str, Any]] = []
        self.edges: List[Dict[str, str]] = []
        self.current_x = start_x
        self.current_y = start_y
        self.spacing_x = 300
        self.spacing_y = 200
        self.last_node_id: Optional[str] = None
    
    def add_node(
        self,
        node_type: str,
        title: str,
        auto_connect: bool = True,
        **kwargs
    ) -> 'WorkflowBuilder':
        """
        Add a node to the workflow.
        
        Args:
            node_type: Type of node
            title: Node title
            auto_connect: Whether to automatically connect to previous node
            **kwargs: Additional node properties
        
        Returns:
            Self for method chaining
        """ 
        node_id = generate_node_id()
        position = {"x": self.current_x, "y": self.current_y}
        
        node = create_node_config(
            node_type=node_type,
            title=title,
            position=position,
            id=node_id,
            **kwargs
        )
        
        self.nodes.append(node)
        
        # Auto-connect to previous node
        if auto_connect and self.last_node_id:
            self.edges.append(create_edge_config(self.last_node_id, node_id))
        
        self.last_node_id = node_id
        self.current_x += self.spacing_x
        
        return self
    
    def add_parallel_nodes(
        self,
        configs: List[Dict[str, Any]],
        connect_from: Optional[str] = None
    ) -> 'WorkflowBuilder':
        """
        Add multiple nodes in parallel (same X position, different Y).
        
        Args:
            configs: List of node configurations
            connect_from: Optional source node ID to connect all parallel nodes from
        
        Returns:
            Self for method chaining
        """ 
        start_y = self.current_y
        source_id = connect_from or self.last_node_id
        
        for i, config in enumerate(configs):
            node_id = generate_node_id()
            position = {"x": self.current_x, "y": start_y + i * self.spacing_y}
            
            node = create_node_config(
                position=position,
                id=node_id,
                **config
            )
            
            self.nodes.append(node)
            
            # Connect from source if specified
            if source_id:
                self.edges.append(create_edge_config(source_id, node_id))
        
        self.current_x += self.spacing_x
        return self
    
    def new_row(self) -> 'WorkflowBuilder':
        """Move to a new row (reset X, increment Y)."""
        self.current_x = 100
        self.current_y += self.spacing_y
        self.last_node_id = None
        return self
    
    def build(self) -> Dict[str, Any]:
        """ Build and return the complete workflow.""" 
        return {
            "nodes": self.nodes,
            "edges": self.edges
        }


# ==================== Node Validation ====================

def validate_node_config(node: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
    """ 验证 a node configuration.
    
    Args:
        node: Node configuration dictionary
    
    Returns:
        Tuple of (is_valid, error_message)
    """ 
    required_fields = ["type", "title", "position"]
    
    for field in required_fields:
        if field not in node:
            return False, f"Missing required field: {field}"
    
    # 验证 node type
    valid_types = [
        "PROMPT_INPUT", "LYRICS_GENERATOR", "IMAGE_GENERATOR", "VIDEO_GENERATOR",
        "VIDEO_ANALYZER", "AUDIO_GENERATOR", "PROMPT_GENERATOR",
        "INFO_DISPLAY", "IMAGE_EDITOR"
    ]
    
    if node["type"] not in valid_types:
        return False, f"Invalid node type: {node['type']}"
    
    # 验证 position
    position = node["position"]
    if not isinstance(position, dict) or "x" not in position or "y" not in position:
        return False, "Invalid position format"
    
    return True, None


def validate_workflow(workflow: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
    """ 验证 a complete workflow configuration.
    
    Args:
        workflow: Workflow dictionary with nodes and edges
    
    Returns:
        Tuple of (is_valid, error_message)
    """
    if "nodes" not in workflow:
        return False, "Missing nodes in workflow"
    
    nodes = workflow["nodes"]
    if not isinstance(nodes, list):
        return False, "Nodes must be a list"
    
    # 验证 each node
    node_ids = set()
    for node in nodes:
        is_valid, error = validate_node_config(node)
        if not is_valid:
            return False, f"Invalid node: {error}"
        
        node_ids.add(node.get("id"))
    
    # 验证 edges if present
    if "edges" in workflow:
        edges = workflow["edges"]
        if not isinstance(edges, list):
            return False, "Edges must be a list"
        
        for edge in edges:
            if "source" not in edge or "target" not in edge:
                return False, "Edge missing source or target"
            
            # Check if referenced nodes exist
            if edge["source"] not in node_ids:
                return False, f"Edge references non-existent source: {edge['source']}"
            if edge["target"] not in node_ids:
                return False, f"Edge references non-existent target: {edge['target']}"
    
    return True, None


# ==================== Node Filtering ====================

def filter_nodes_by_type(
    nodes: List[Dict[str, Any]],
    node_type: str
) -> List[Dict[str, Any]]:
    """ Filter nodes by type.""" 
    return [n for n in nodes if n.get("type") == node_type]


def filter_nodes_by_status(
    nodes: List[Dict[str, Any]],
    status: str
) -> List[Dict[str, Any]]:
    """ Filter nodes by status.""" 
    return [n for n in nodes if n.get("status") == status]


def find_connected_nodes(
    node_id: str,
    edges: List[Dict[str, str]],
    direction: str = "both"
) -> List[str]:
    """ Find nodes connected to a given node.
    
    Args:
        node_id: Target node ID
        edges: List of edge configurations
        direction: "incoming", "outgoing", or "both"
    
    Returns:
        List of connected node IDs
    """ 
    connected = []
    
    if direction in ["incoming", "both"]:
        connected.extend([
            e["source"] for e in edges if e["target"] == node_id
        ])
    
    if direction in ["outgoing", "both"]:
        connected.extend([
            e["target"] for e in edges if e["source"] == node_id
        ])
    
    return list(set(connected))


# ==================== Workflow Analysis ====================

def analyze_workflow_complexity(workflow: Dict[str, Any]) -> Dict[str, Any]:
    """
    Analyze workflow complexity and provide metrics.
    
    Args:
        workflow: Workflow configuration
    
    Returns:
        Dictionary with complexity metrics
    """ 
    nodes = workflow.get("nodes", [])
    edges = workflow.get("edges", [])
    
    # 计数 node types
    node_type_counts = {}
    for node in nodes:
        node_type = node.get("type", "UNKNOWN")
        node_type_counts[node_type] = node_type_counts.get(node_type, 0) + 1
    
    # Calculate graph metrics
    node_ids = {n.get("id") for n in nodes}
    
    # 查找 source nodes (no incoming edges)
    targets = {e["target"] for e in edges}
    sources = node_ids - targets
    
    # 查找 sink nodes (no outgoing edges)
    edge_sources = {e["source"] for e in edges}
    sinks = node_ids - edge_sources
    
    # Calculate average connections per node
    avg_connections = len(edges) / len(nodes) if nodes else 0
    
    return {
        "total_nodes": len(nodes),
        "total_edges": len(edges),
        "node_type_counts": node_type_counts,
        "source_nodes": len(sources),
        "sink_nodes": len(sinks),
        "avg_connections_per_node": round(avg_connections, 2),
        "is_linear": len(edges) == len(nodes) - 1 and len(sources) == 1 and len(sinks) == 1
    }


# ==================== Export/Import ====================

def export_workflow_to_json(workflow: Dict[str, Any]) -> str:
    """ Export workflow to JSON string.""" 
    import json
    return json.dumps(workflow, indent=2, ensure_ascii=False)


def import_workflow_from_json(json_str: str) -> Dict[str, Any]:
    """ Import workflow from JSON string.""" 
    import json
    return json.loads(json_str)


# ==================== Preset Configurations ====================

PRESET_MODELS = {
    "image": {
        "high_quality": "flux-1.1-pro",
        "fast": "flux-schnell",
        "default": "flux-1.1-pro"
    },
    "video": {
        "high_quality": "kling-v1.6-pro",
        "fast": "kling-v1.5",
        "default": "kling-v1.6-pro"
    }
}

PRESET_ASPECT_RATIOS = {
    "square": "1:1",
    "landscape": "16:9",
    "portrait": "9:16",
    "wide": "21:9",
    "ultrawide": "32:9"
}

PRESET_TEMPLATES = {
    "character_white_bg": {
        "targetType": "image",
        "template": "character_white_bg"
    },
    "character_three_view": {
        "targetType": "image",
        "template": "character_three_view"
    },
    "storyboard": {
        "targetType": "image",
        "template": "storyboard_integrated"
    },
    "asset_360": {
        "targetType": "video",
        "template": "asset_360"
    }
}


def get_preset_config(category: str, preset: str) -> Any:
    """ 获取 a preset configuration value."""
    presets = {
        "models": PRESET_MODELS,
        "aspect_ratios": PRESET_ASPECT_RATIOS,
        "templates": PRESET_TEMPLATES
    }
    
    if category not in presets:
        return None
    
    config = presets[category]
    
    # Handle nested access (e.g., "image.high_quality")
    if "." in preset:
        keys = preset.split(".")
        for key in keys:
            if isinstance(config, dict) and key in config:
                config = config[key]
            else:
                return None
        return config
    
    return config.get(preset)
