"""Shared utilities for LLM API interaction and message extraction.

Consolidates the duplicated _extract_text / _extract_reply_text pattern
(previously repeated in 5 places) and _extract_content_from_api_response
(previously duplicated in 2 places).
"""

from typing import Any


def extract_reply_text(reply: Any) -> str:
    """Extract plain text from a tutu-api reply object.

    Handles: None | str | dict with msgBody.content or content key.
    """
    if reply is None:
        return ""
    if isinstance(reply, str):
        return reply
    if isinstance(reply, dict):
        body = reply.get("msgBody") or reply.get("content", "")
        if isinstance(body, dict):
            return body.get("content", "")
        return str(body)
    return str(reply)


def extract_content_from_llm_response(data: dict) -> str:
    """Extract text content from an LLM API response dict.

    Handles:
    - OpenAI format: choices[0].message.content as a plain string
    - Anthropic-compatible format: choices[0].message.content as a list of
      content blocks {type: "text", text: "..."}  (non-text blocks are skipped)
    """
    try:
        content = data["choices"][0]["message"]["content"]
    except (KeyError, IndexError, TypeError):
        return ""
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        parts = []
        for block in content:
            if not isinstance(block, dict):
                continue
            if block.get("type") == "text":
                parts.append(block.get("text") or block.get("content") or "")
        return "\n".join(parts)
    return str(content)


def parse_json_from_llm_text(content: str) -> Any:
    """Parse JSON from LLM output, falling back to bracket-delimited substring.

    Returns parsed JSON object, or raises json.JSONDecodeError if unparseable.
    """
    import json

    try:
        return json.loads(content)
    except json.JSONDecodeError:
        pass

    # Fallback: find the outermost JSON object or array
    for open_char, close_char in [("{", "}"), ("[", "]")]:
        start = content.find(open_char)
        end = content.rfind(close_char)
        if start != -1 and end != -1 and end > start:
            return json.loads(content[start : end + 1])

    raise ValueError(f"No JSON found in LLM output: {content[:200]}")
