import requests
import json
from typing import Optional, Dict, Any, List, Literal
import os
import uuid
from enum import Enum
from blocks_control_sdk.utils import remove_newlines_and_special_characters, get_blocks_runtime_config, BlocksRuntimeConfigKeys
from blocks_control_sdk.tools.blocks import get_task_header

LINEAR_TOKEN = os.getenv("LINEAR_TOKEN")


def get_agent_session_id() -> Optional[str]:
    """Get the agent session ID dynamically (env var may be set after import)."""
    return os.getenv("LINEAR_AGENT_SESSION_ID")


# ============================================================================
# Agent Activity Types (Linear Agent Interaction Pattern)
# ============================================================================

class LinearAgentActivityType(str, Enum):
    """Agent Activity types for communicating progress to Linear"""
    THOUGHT = "thought"
    ACTION = "action"
    ELICITATION = "elicitation"
    RESPONSE = "response"
    ERROR = "error"


class LinearAgentPlanStatus(str, Enum):
    """Agent Plan step status"""
    PENDING = "pending"
    IN_PROGRESS = "inProgress"
    COMPLETED = "completed"
    CANCELED = "canceled"


def download_linear_image(image_url: str, save_path: Optional[str] = None) -> Optional[str]:
    """
    Download an image from Linear.

    Args:
        image_url: The full URL of the Linear image (e.g., https://uploads.linear.app/...)
        save_path: Optional path to save the image. If not provided, saves to /tmp with original filename

    Returns:
        The path where the image was saved, or None if failed
    """
    print("="*100)
    print(f"TOOL CALL: Fetching Linear image from: {image_url}")
    print("="*100)

    try:
        # Make the request to fetch the image
        response = requests.get(
            image_url,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
            },
            timeout=30
        )

        # Check if request was successful
        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None

        # Determine save path
        if save_path is None:
            # Extract filename from URL
            filename = image_url.split('/')[-1]
            if not "." in (filename or ""):
                # Try to determine extension from Content-Type header
                extension = "jpg"  # default fallback
                try:
                    content_type = response.headers.get('Content-Type', '').lower()
                    if content_type:
                        # Map common image MIME types to extensions
                        mime_to_ext = {
                            'image/jpeg': 'jpg',
                            'image/jpg': 'jpg',
                            'image/png': 'png',
                            'image/gif': 'gif',
                            'image/webp': 'webp',
                            'image/svg+xml': 'svg',
                            'image/bmp': 'bmp',
                            'image/tiff': 'tiff',
                            'image/x-icon': 'ico',
                        }
                        # Extract base MIME type (ignore parameters like charset)
                        base_mime = content_type.split(';')[0].strip()
                        extension = mime_to_ext.get(base_mime, 'jpg')
                        print(f"Detected extension '{extension}' from Content-Type: {content_type}")
                except Exception as e:
                    print(f"Could not determine extension from Content-Type, using default: {e}")

                filename = f"{uuid.uuid4()}.{extension}"
            save_path = f"/tmp/{filename}"

        # Save the image
        with open(save_path, 'wb') as f:
            f.write(response.content)

        success_msg = f"Successfully saved Linear image to: {save_path}"
        return success_msg

    except requests.exceptions.RequestException as e:
        print(f"Error fetching Linear image: {e}")
        return None
    except IOError as e:
        print(f"Error saving Linear image to disk: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error fetching Linear image: {e}")
        return None


def create_linear_issue_comment(
    issue_id: str,
    body: str,
    parent_id: Optional[str] = None,
    display_icon_url: Optional[str] = None,
    create_as_user: Optional[str] = None
) -> Optional[Dict[str, Any]]:
    """
    Create a comment on a Linear issue using GraphQL API.
    
    Args:
        issue_id: The Linear issue ID to comment on
        body: The comment text/body
        access_token: Linear OAuth access token
        parent_id: Optional parent comment ID for threaded replies
        display_icon_url: Optional custom icon URL for the comment
        create_as_user: Optional user identifier to create comment on behalf of
        
    Returns:
        Dict containing the created comment data, or None if failed
    """
    print("="*100)
    print(f"TOOL CALL: Creating Linear issue comment on issue: {issue_id}")
    print("="*100)
    
    # GraphQL mutation for creating a comment
    mutation = """
      mutation($input: CommentCreateInput!) {
        commentCreate(input: $input) {
          success
          comment {
            id
            body
            createdAt
            user {
              id
              name
              email
              displayName
            }
            issue {
              id
              identifier
              title
            }
            parent {
              id
            }
            editedAt
            url
          }
        }
      }
    """
    
    try:
        # Build the input object
        input_data = {
            "issueId": issue_id,
            "body": body
        }
        
        # Add optional fields if provided
        if parent_id:
            input_data["parentId"] = parent_id
        if display_icon_url:
            input_data["displayIconUrl"] = display_icon_url
        if create_as_user:
            input_data["createAsUser"] = create_as_user
        
        # Prepare the request payload
        payload = {
            "query": mutation,
            "variables": {"input": input_data}
        }
        
        # Make the GraphQL request
        response = requests.post(
            "https://api.linear.app/graphql",
            json=payload,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
                "Content-Type": "application/json"
            },
            timeout=30
        )
        
        # Check if request was successful
        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None
        
        # Parse the response
        response_data = response.json()
        
        if not response_data:
            print("Linear API response is null or empty")
            return None
            
        # Check for GraphQL errors
        if "errors" in response_data:
            print(f"Linear GraphQL errors: {response_data['errors']}")
            return None
            
        # Extract the comment creation result
        comment_create_result = response_data.get("data", {}).get("commentCreate")
        
        if not comment_create_result:
            print("No commentCreate result in response")
            return None
            
        if not comment_create_result.get("success"):
            print("Comment creation was not successful")
            return None
            
        created_comment = comment_create_result.get("comment")
        if created_comment:
            print(f"Successfully created Linear comment with ID: {created_comment.get('id')}")
            
        return comment_create_result
        
    except requests.exceptions.RequestException as e:
        print(f"Error making request to Linear API: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Error parsing Linear API response: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error creating Linear issue comment: {e}")
        return None


def update_linear_issue_comment_with_header(comment_id: str, body: str = ""):
    """
    Update the Linear issue comment with a header containing the task progress.
    """
    print("="*100)
    print(f"TOOL CALL: Updating Linear issue comment with header: {comment_id}")
    print("="*100)
    
    issue_body_header = get_task_header()
    if body:
        # strip newlines
        body = remove_newlines_and_special_characters(body)
        issue_body_header += f"\n> {body}"
    
    # Use the update_linear_issue_comment function to update the comment
    return update_linear_issue_comment(comment_id, issue_body_header)


def update_linear_issue_comment(
    comment_id: str,
    body: str,
) -> Optional[Dict[str, Any]]:
    """
    Update an existing Linear issue comment using GraphQL API.
    
    Args:
        comment_id: The Linear comment ID to update
        body: The new comment text/body
        access_token: Linear OAuth access token
        
    Returns:
        Dict containing the updated comment data, or None if failed
    """
    print("="*100)
    print(f"TOOL CALL: Updating Linear issue comment: {comment_id}")
    print("="*100)
    
    # GraphQL mutation for updating a comment
    mutation = """
      mutation($id: String!, $input: CommentUpdateInput!) {
        commentUpdate(id: $id, input: $input) {
          success
          comment {
            id
            body
            createdAt
            editedAt
            user {
              id
              name
              email
              displayName
            }
            issue {
              id
              identifier
              title
            }
            parent {
              id
            }
            url
          }
        }
      }
    """
    
    try:
        # Build the input object - only contains the body
        input_data = {
            "body": body
        }
        
        # Prepare the request payload
        payload = {
            "query": mutation,
            "variables": {
                "id": comment_id,
                "input": input_data
            }
        }
        
        # Make the GraphQL request
        response = requests.post(
            "https://api.linear.app/graphql",
            json=payload,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
                "Content-Type": "application/json"
            },
            timeout=30
        )
        
        # Check if request was successful
        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None
        
        # Parse the response
        response_data = response.json()
        
        if not response_data:
            print("Linear API response is null or empty")
            return None
            
        # Check for GraphQL errors
        if "errors" in response_data:
            print(f"Linear GraphQL errors: {response_data['errors']}")
            return None
            
        # Extract the comment update result
        comment_update_result = response_data.get("data", {}).get("commentUpdate")
        
        if not comment_update_result:
            print("No commentUpdate result in response")
            return None
            
        if not comment_update_result.get("success"):
            print("Comment update was not successful")
            return None
            
        updated_comment = comment_update_result.get("comment")
        if updated_comment:
            print(f"Successfully updated Linear comment with ID: {updated_comment.get('id')}")
            
        return comment_update_result
        
    except requests.exceptions.RequestException as e:
        print(f"Error making request to Linear API: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Error parsing Linear API response: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error updating Linear issue comment: {e}")
        return None


def update_message__linear(message: str, include_user_message: bool = False) -> Optional[Dict[str, Any]]:
    """
    Update Linear issue comment with the final message, optionally including user message block.
    Similar to update_message__github() but adapted for Linear.

    Handles both agent session flow (create_response_activity) and legacy comment flow
    (update_linear_issue_comment) internally.

    Args:
        message: The final message to update (markdown string)
        include_user_message: Whether to include the user message as a quoted block

    Returns:
        Result from either create_response_activity or update_linear_issue_comment
    """
    print("="*100)
    print(f"[Action] Updating Linear message")
    print(f"Include user message: {include_user_message}")
    print("="*100)

    blocks_runtime_config = get_blocks_runtime_config()
    comment_id = blocks_runtime_config.get("metadata", {}).get("comment_id")

    if not comment_id and not is_agent_session_active():
        print("No comment id found and no agent session active")
        return None

    # Get the latest user message source provider to check if we should include it
    from blocks_control_sdk.blocks_events.context import BLOCKS_EVENT__PROVIDER
    latest_user_message_source_provider = blocks_runtime_config.get(
        BlocksRuntimeConfigKeys.LATEST_USER_MESSAGE_SOURCE_PROVIDER
    )

    final_message = message

    # If include_user_message is True AND the message did NOT originate from Linear
    # Then prepend the user message as a quoted block (similar to GitHub behavior)
    if include_user_message and latest_user_message_source_provider != BLOCKS_EVENT__PROVIDER.LINEAR.value:
        latest_user_message = blocks_runtime_config.get(BlocksRuntimeConfigKeys.LATEST_USER_MESSAGE, "")

        if latest_user_message:
            # Truncate and clean the user message
            latest_user_message = remove_newlines_and_special_characters(latest_user_message)[:500]

            # Format as markdown blockquote
            user_message_block = f"**User Message:**\n> {latest_user_message}\n\n"
            final_message = user_message_block + message

    # Check if using agent session pattern or legacy comment pattern
    use_agent_session = blocks_runtime_config.get("metadata", {}).get("use_agent_session", False)

    if use_agent_session or is_agent_session_active():
        # Use agent response activity instead of comment update
        print("Using agent session flow - creating response activity")
        return create_response_activity(final_message)
    else:
        # Legacy comment-based flow
        print(f"Using legacy comment flow - updating comment {comment_id}")
        if comment_id:
            return update_linear_issue_comment(comment_id, final_message)
        return None


def notify_linear_progress(summary: str) -> None:
    """
    Fan-out method for Linear progress notifications.
    Agent session path: creates an ephemeral thought activity.
    Legacy path: updates the Linear issue comment with a header.
    """
    blocks_runtime_config = get_blocks_runtime_config()
    use_agent_session = blocks_runtime_config.get("metadata", {}).get("use_agent_session", False)

    if use_agent_session or is_agent_session_active():
        create_thought_activity(summary, ephemeral=True)
    else:
        comment_id = blocks_runtime_config.get("metadata", {}).get("comment_id")
        if comment_id and summary:
            update_linear_issue_comment_with_header(comment_id, summary)


def notify_linear_tool_call(tool_name: str, serialized_args: str) -> None:
    """
    Fan-out method for Linear tool call notifications.
    Agent session path: creates an ephemeral action activity.
    Legacy path: no-op (tool calls are not reported in legacy comment flow).
    """
    blocks_runtime_config = get_blocks_runtime_config()
    use_agent_session = blocks_runtime_config.get("metadata", {}).get("use_agent_session", False)

    if use_agent_session or is_agent_session_active():
        truncated_args = serialized_args[:200] + "..." if len(serialized_args) > 200 else serialized_args
        create_action_activity(
            action=tool_name,
            parameter=truncated_args,
            ephemeral=True
        )


# ============================================================================
# Agent Session & Activity Functions (Linear Agent Interaction Pattern)
# @see https://linear.app/developers/agent-interaction
# ============================================================================


def create_agent_activity(
    content: Dict[str, Any],
    session_id: Optional[str] = None,
    ephemeral: bool = False
) -> Optional[Dict[str, Any]]:
    """
    Create an agent activity for a Linear agent session.

    Args:
        content: Activity content dict with 'type' and type-specific fields:
            - thought: {"type": "thought", "body": "..."}
            - action: {"type": "action", "action": "...", "parameter": "...", "result": "..."}
            - elicitation: {"type": "elicitation", "body": "..."}
            - response: {"type": "response", "body": "..."}
            - error: {"type": "error", "body": "..."}
        session_id: Optional agent session ID (defaults to LINEAR_AGENT_SESSION_ID env var)
        ephemeral: If True, activity will be replaced by next activity (only for thought/action)

    Returns:
        Dict containing the created activity data, or None if failed
    """
    session_id = session_id or get_agent_session_id()

    if not session_id:
        print("No agent session ID available, skipping activity creation")
        return None

    print("="*100)
    print(f"TOOL CALL: Creating Linear agent activity for session: {session_id}")
    print(f"Activity type: {content.get('type')}")
    print("="*100)

    mutation = """
      mutation AgentActivityCreate($input: AgentActivityCreateInput!) {
        agentActivityCreate(input: $input) {
          success
          agentActivity {
            id
            createdAt
          }
        }
      }
    """

    try:
        input_data = {
            "agentSessionId": session_id,
            "content": content
        }

        if ephemeral:
            input_data["ephemeral"] = True

        payload = {
            "query": mutation,
            "variables": {"input": input_data}
        }

        response = requests.post(
            "https://api.linear.app/graphql",
            json=payload,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
                "Content-Type": "application/json"
            },
            timeout=30
        )

        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None

        response_data = response.json()

        if not response_data:
            print("Linear API response is null or empty")
            return None

        if "errors" in response_data:
            print(f"Linear GraphQL errors: {response_data['errors']}")
            return None

        result = response_data.get("data", {}).get("agentActivityCreate")

        if result and result.get("success"):
            activity = result.get("agentActivity")
            if activity:
                print(f"Successfully created agent activity with ID: {activity.get('id')}")

        return result

    except requests.exceptions.RequestException as e:
        print(f"Error making request to Linear API: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Error parsing Linear API response: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error creating agent activity: {e}")
        return None


def create_thought_activity(body: str, ephemeral: bool = False) -> Optional[Dict[str, Any]]:
    """Create a thought/internal note activity."""
    return create_agent_activity(
        content={"type": LinearAgentActivityType.THOUGHT.value, "body": body},
        ephemeral=ephemeral
    )


def create_action_activity(
    action: str,
    parameter: Optional[str] = None,
    result: Optional[str] = None,
    ephemeral: bool = False
) -> Optional[Dict[str, Any]]:
    """Create an action/tool invocation activity."""
    content: Dict[str, Any] = {
        "type": LinearAgentActivityType.ACTION.value,
        "action": action
    }
    if parameter is not None:
        content["parameter"] = parameter
    if result is not None:
        content["result"] = result

    return create_agent_activity(content=content, ephemeral=ephemeral)


def create_elicitation_activity(body: str) -> Optional[Dict[str, Any]]:
    """Create an elicitation activity (request for user clarification)."""
    return create_agent_activity(
        content={"type": LinearAgentActivityType.ELICITATION.value, "body": body}
    )


def create_response_activity(body: str) -> Optional[Dict[str, Any]]:
    """Create a response activity (work complete/final result)."""
    return create_agent_activity(
        content={"type": LinearAgentActivityType.RESPONSE.value, "body": body}
    )


def create_error_activity(body: str) -> Optional[Dict[str, Any]]:
    """Create an error activity."""
    return create_agent_activity(
        content={"type": LinearAgentActivityType.ERROR.value, "body": body}
    )


def update_agent_session(
    session_id: Optional[str] = None,
    plan: Optional[List[Dict[str, str]]] = None,
    external_urls: Optional[List[Dict[str, str]]] = None,
    added_external_urls: Optional[List[Dict[str, str]]] = None,
    removed_external_urls: Optional[List[str]] = None
) -> Optional[Dict[str, Any]]:
    """
    Update an agent session (plan, external URLs).

    Args:
        session_id: Optional agent session ID (defaults to LINEAR_AGENT_SESSION_ID env var)
        plan: Full array of plan steps to replace existing plan. Each step has:
            - content: string description of the step
            - status: "pending" | "inProgress" | "completed" | "canceled"
        external_urls: Array of external URLs to replace existing URLs. Each has:
            - label: display label
            - url: URL string
        added_external_urls: URLs to add (without removing existing)
        removed_external_urls: URL strings to remove

    Returns:
        Dict containing the updated session data, or None if failed
    """
    session_id = session_id or get_agent_session_id()

    if not session_id:
        print("No agent session ID available, skipping session update")
        return None

    print("="*100)
    print(f"TOOL CALL: Updating Linear agent session: {session_id}")
    print("="*100)

    mutation = """
      mutation AgentSessionUpdate($id: String!, $input: AgentSessionUpdateInput!) {
        agentSessionUpdate(id: $id, input: $input) {
          success
          agentSession {
            id
            state
            plan {
              content
              status
            }
            externalUrls {
              label
              url
            }
          }
        }
      }
    """

    try:
        input_data: Dict[str, Any] = {}

        if plan is not None:
            input_data["plan"] = plan
        if external_urls is not None:
            input_data["externalUrls"] = external_urls
        if added_external_urls is not None:
            input_data["addedExternalUrls"] = added_external_urls
        if removed_external_urls is not None:
            input_data["removedExternalUrls"] = removed_external_urls

        if not input_data:
            print("No update data provided")
            return None

        payload = {
            "query": mutation,
            "variables": {"id": session_id, "input": input_data}
        }

        response = requests.post(
            "https://api.linear.app/graphql",
            json=payload,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
                "Content-Type": "application/json"
            },
            timeout=30
        )

        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None

        response_data = response.json()

        if not response_data:
            print("Linear API response is null or empty")
            return None

        if "errors" in response_data:
            print(f"Linear GraphQL errors: {response_data['errors']}")
            return None

        result = response_data.get("data", {}).get("agentSessionUpdate")

        if result and result.get("success"):
            print(f"Successfully updated agent session {session_id}")

        return result

    except requests.exceptions.RequestException as e:
        print(f"Error making request to Linear API: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Error parsing Linear API response: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error updating agent session: {e}")
        return None


def add_external_url_to_session(label: str, url: str) -> Optional[Dict[str, Any]]:
    """
    Add an external URL to the current agent session.

    Args:
        label: Display label for the URL (e.g., "PR #123", "Build Results")
        url: The URL to add

    Returns:
        Dict containing the updated session data, or None if failed
    """
    session_id = get_agent_session_id()
    if not session_id:
        print("No agent session ID available, skipping external URL addition")
        return None

    print("="*100)
    print(f"TOOL CALL: Adding external URL to Linear agent session: {label} -> {url}")
    print("="*100)

    return update_agent_session(
        session_id=session_id,
        added_external_urls=[{"label": label, "url": url}]
    )


def sync_plan_to_linear(todos: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    """
    Sync todo list to Linear agent session plan.

    Args:
        todos: List of todo items from TodoWrite tool, each with:
            - content: task description
            - status: "pending" | "in_progress" | "completed"
            - activeForm: present continuous form of the task (optional)

    Returns:
        Dict containing the updated session data, or None if failed
    """
    # Map internal status to Linear plan status
    status_map = {
        "pending": LinearAgentPlanStatus.PENDING.value,
        "in_progress": LinearAgentPlanStatus.IN_PROGRESS.value,
        "completed": LinearAgentPlanStatus.COMPLETED.value
    }

    plan = []
    total_tasks = len(todos)

    for idx, todo in enumerate(todos):
        internal_status = todo.get("status", "pending")
        linear_status = status_map.get(internal_status, LinearAgentPlanStatus.PENDING.value)
        plan.append({
            "content": todo.get("content", ""),
            "status": linear_status
        })

        # Emit thought activities for plan progress visibility
        task_num = idx + 1
        if internal_status == "in_progress":
            # Use activeForm if available, otherwise use content
            task_description = todo.get("activeForm", todo.get("content", ""))
            create_thought_activity(
                body=f"Step {task_num}/{total_tasks}: {task_description}",
                ephemeral=False  # Keep as permanent record
            )
        elif internal_status == "completed":
            task_description = todo.get("content", "")
            create_thought_activity(
                body=f"✓ Completed step {task_num}/{total_tasks}: {task_description}",
                ephemeral=False  # Keep as permanent record
            )

    return update_agent_session(plan=plan)


def update_issue_state(
    issue_id: str,
    state_id: str
) -> Optional[Dict[str, Any]]:
    """
    Update a Linear issue's workflow state.

    Args:
        issue_id: The Linear issue ID
        state_id: The workflow state ID to set

    Returns:
        Dict containing the update result, or None if failed
    """
    print("="*100)
    print(f"TOOL CALL: Updating Linear issue state: {issue_id} -> {state_id}")
    print("="*100)

    mutation = """
      mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) {
        issueUpdate(id: $id, input: $input) {
          success
          issue {
            id
            state {
              id
              name
            }
          }
        }
      }
    """

    try:
        payload = {
            "query": mutation,
            "variables": {
                "id": issue_id,
                "input": {"stateId": state_id}
            }
        }

        response = requests.post(
            "https://api.linear.app/graphql",
            json=payload,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
                "Content-Type": "application/json"
            },
            timeout=30
        )

        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None

        response_data = response.json()

        if not response_data:
            print("Linear API response is null or empty")
            return None

        if "errors" in response_data:
            print(f"Linear GraphQL errors: {response_data['errors']}")
            return None

        result = response_data.get("data", {}).get("issueUpdate")

        if result and result.get("success"):
            issue = result.get("issue", {})
            state = issue.get("state", {})
            print(f"Successfully updated issue {issue_id} state to {state.get('name')}")

        return result

    except requests.exceptions.RequestException as e:
        print(f"Error making request to Linear API: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Error parsing Linear API response: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error updating issue state: {e}")
        return None


def get_team_workflow_states(team_id: str) -> Optional[List[Dict[str, Any]]]:
    """
    Get workflow states for a Linear team.

    Args:
        team_id: The Linear team ID

    Returns:
        List of workflow state dicts, or None if failed
    """
    print("="*100)
    print(f"TOOL CALL: Getting workflow states for team: {team_id}")
    print("="*100)

    query = """
      query($teamId: String!) {
        team(id: $teamId) {
          states {
            nodes {
              id
              name
              type
              position
            }
          }
        }
      }
    """

    try:
        payload = {
            "query": query,
            "variables": {"teamId": team_id}
        }

        response = requests.post(
            "https://api.linear.app/graphql",
            json=payload,
            headers={
                "Authorization": f"Bearer {LINEAR_TOKEN}",
                "Content-Type": "application/json"
            },
            timeout=30
        )

        if response.status_code != 200:
            print(f"HTTP Error {response.status_code}: {response.text}")
            return None

        response_data = response.json()

        if not response_data:
            print("Linear API response is null or empty")
            return None

        if "errors" in response_data:
            print(f"Linear GraphQL errors: {response_data['errors']}")
            return None

        states = response_data.get("data", {}).get("team", {}).get("states", {}).get("nodes", [])
        print(f"Found {len(states)} workflow states")

        return states

    except requests.exceptions.RequestException as e:
        print(f"Error making request to Linear API: {e}")
        return None
    except json.JSONDecodeError as e:
        print(f"Error parsing Linear API response: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error getting workflow states: {e}")
        return None


def is_agent_session_active() -> bool:
    """Check if we're running in an agent session context."""
    return bool(get_agent_session_id())


def report_error_to_session(
    error_type: str,
    error_message: str,
    context: str = "",
    recovery_action: str = ""
):
    """
    Report a structured error to the Linear agent session if active.

    Args:
        error_type: Type of error (e.g., "Tool Execution Failed", "Timeout", "API Error")
        error_message: The error message
        context: What was being attempted when the error occurred
        recovery_action: What will happen next (retry, skip, abort)
    """
    if is_agent_session_active():
        error_body = f"**{error_type}**\n\n"
        if context:
            error_body += f"Context: {context}\n\n"
        error_body += f"Error: {error_message}\n\n"
        if recovery_action:
            error_body += f"Recovery: {recovery_action}"

        create_error_activity(body=error_body)


def report_retry_attempt(operation: str, attempt: int, max_attempts: int, ephemeral: bool = True):
    """
    Report a retry attempt to the Linear agent session if active.

    Args:
        operation: The operation being retried
        attempt: Current attempt number
        max_attempts: Maximum number of attempts
        ephemeral: Whether this should be an ephemeral thought (replaced when done)
    """
    if is_agent_session_active():
        create_thought_activity(
            body=f"Retrying {operation} (attempt {attempt}/{max_attempts})...",
            ephemeral=ephemeral
        )
