import json
import os
import re

import json5

from mem_deep_research_core.mem_deep_research_logging.logger import bootstrap_logger

LOGGER_LEVEL = os.getenv("LOGGER_LEVEL", "INFO")
logger = bootstrap_logger(level=LOGGER_LEVEL)


def _smart_json_truncate(json_str):
    """
    Intelligently truncate a JSON string at the end of the first complete structure, removing any possible extra characters.
    Supports top-level structures starting with { or [; does not count braces inside strings.
    """
    if not json_str:
        return json_str

    # Find the first non-whitespace character to determine the top-level structure type
    start = 0
    while start < len(json_str) and json_str[start].isspace():
        start += 1
    if start >= len(json_str):
        return json_str

    first_char = json_str[start]
    if first_char not in ("{", "["):
        # For non-object/array cases (e.g., plain string, number), keep as is
        return json_str

    open_char = "{" if first_char == "{" else "["
    close_char = "}" if first_char == "{" else "]"

    depth = 0
    in_string = False
    escape_next = False

    for i in range(start, len(json_str)):
        ch = json_str[i]
        if escape_next:
            escape_next = False
            continue
        if ch == "\\":
            escape_next = True
            continue
        if ch == '"':
            in_string = not in_string
            continue
        if in_string:
            continue
        if ch == open_char:
            depth += 1
        elif ch == close_char:
            depth -= 1
            if depth == 0:
                return json_str[: i + 1]

    return json_str


def _fix_unterminated_string_values(json_str):
    """
    General fix: minimally repair when some string values are missing a closing double quote.
    Only when a pattern like "key": "<value... is detected and there is no unescaped '"' until the end of input,
    add a '"' just before the top-level closing brace/bracket.
    Does not depend on specific key names, tries to be conservative to avoid incorrect changes.
    """
    try:
        # Regex to match patterns like "key" : " (key supports escape characters)
        pattern = re.compile(r'"(?:[^"\\]|\\.)*"\s*:\s*"', re.DOTALL)
        for m in pattern.finditer(json_str):
            value_start = m.end()

            # From value_start, look for an unescaped closing quote
            i = value_start
            escaped = False
            closed = False
            while i < len(json_str):
                ch = json_str[i]
                if escaped:
                    escaped = False
                    i += 1
                    continue
                if ch == "\\":
                    escaped = True
                    i += 1
                    continue
                if ch == '"':
                    # Found a potential closing quote, check if it's properly positioned
                    j = i + 1
                    while j < len(json_str) and json_str[j].isspace():
                        j += 1

                    # More lenient check: if followed by valid JSON separators OR end of valid JSON structure
                    if j >= len(json_str) or json_str[j] in (",", "}", "]"):
                        closed = True
                        break
                    # If followed by non-JSON text, this might be an embedded quote, continue searching
                i += 1

            if not closed:
                # Look for the best place to add closing quote
                # First try to find a closing brace or bracket
                end = len(json_str) - 1
                while end >= 0 and json_str[end].isspace():
                    end -= 1

                if end >= 0 and json_str[end] in ("}", "]"):
                    # Add quote before the closing brace/bracket
                    fixed = json_str[:end] + '"' + json_str[end:]

                    # Minimal balancing: append missing closing braces/brackets
                    open_curly = close_curly = open_square = close_square = 0
                    in_str = False
                    esc = False
                    for ch in fixed:
                        if esc:
                            esc = False
                            continue
                        if ch == "\\":
                            esc = True
                            continue
                        if ch == '"':
                            in_str = not in_str
                            continue
                        if in_str:
                            continue
                        if ch == "{":
                            open_curly += 1
                        elif ch == "}":
                            close_curly += 1
                        elif ch == "[":
                            open_square += 1
                        elif ch == "]":
                            close_square += 1

                    if open_curly > close_curly:
                        fixed += "}" * (open_curly - close_curly)
                    if open_square > close_square:
                        fixed += "]" * (open_square - close_square)

                    return fixed
                else:
                    # If no closing brace/bracket found, try to find where JSON should end
                    # Look for the end of what looks like a value and add quote + closing brace
                    for k in range(len(json_str) - 1, value_start - 1, -1):
                        if json_str[k] not in (" ", "\t", "\n", "\r"):
                            # Add closing quote and brace after the last non-whitespace character
                            return json_str[: k + 1] + '"}'
        return json_str
    except Exception:
        return json_str


def preprocess_json_string(json_str):
    """
    Preprocess JSON string to fix common issues before parsing.

    Applies universal fixes that help with LLM-generated JSON:
    1. Fix unterminated string values (to make JSON structure complete)
    2. Smart truncation to remove extra content after valid JSON

    Args:
        json_str (str): The raw JSON string to preprocess

    Returns:
        str: The preprocessed JSON string
    """
    if not json_str or not isinstance(json_str, str):
        return json_str

    # First fix unterminated string values to make JSON structure complete
    processed = _fix_unterminated_string_values(json_str)

    # Then apply smart truncation to remove extra content
    processed = _smart_json_truncate(processed)

    return processed


def robust_json_loads(json_str, apply_preprocessing=True):
    """
    Robust JSON parsing function with optional preprocessing.

    First applies preprocessing to fix common LLM JSON issues, then tries
    standard json, fallback to json5 if that fails.
    Args:
        json_str (str): The JSON string to parse
        apply_preprocessing (bool): Whether to apply preprocessing fixes. Default True.
    Returns:
        dict: The parsed JSON object
    Raises:
        json.JSONDecodeError: If all parsing attempts fail
    """
    # Apply preprocessing by default to fix common issues
    if apply_preprocessing:
        json_str = preprocess_json_string(json_str)

    # First try standard json
    try:
        return json.loads(json_str)
    except json.JSONDecodeError as e:
        logger.debug(f"Standard JSON parsing failed: {e}")

        # If json5 is available, try json5 parsing
        if json5 is not None:
            try:
                return json5.loads(json_str)
            except Exception as e2:
                logger.debug(f"JSON5 parsing also failed: {e2}")

        # If both fail, re-raise the original exception
        raise e


def escape_string_content(content, key_name=None):
    """
    Smart escaping and fixing: different processing based on key type

    Escaping strategy:
    - Basic escaping: double quotes, newlines and other JSON-required escaping
    - Smart fixing: fix common syntax errors based on key type
      * code_block: null→None, true→True, false→False
      * command: True→true, False→false, None→""
      * others: None→null, True→true, False→false

    Args:
        content (str): The string content to escape
        key_name (str): The key name, used to determine fixing strategy

    Returns:
        str: The escaped and fixed string
    """
    # Strategy 1: Basic escaping (needed for all fields)
    result = []
    i = 0

    while i < len(content):
        char = content[i]

        if char == "\\" and i + 1 < len(content):
            # Found backslash, keep escape sequence as is (including \" and \\n etc.)
            result.append(char)  # Add backslash
            result.append(content[i + 1])  # Add next character
            i += 2  # Skip two characters

        elif char == '"':
            # Unescaped double quote, needs escaping
            result.append('\\"')
            i += 1

        elif char == "\n":
            # Unescaped newline, needs escaping (JSON standard requirement)
            result.append("\\n")
            i += 1

        elif char == "\r":
            # Unescaped carriage return, needs escaping
            result.append("\\r")
            i += 1

        else:
            # Other characters remain as is
            result.append(char)
            i += 1

    content_escaped = "".join(result)

    # Strategy 2: Smart fixing based on key type
    if key_name == "code_block":
        # Python code fixing
        content_escaped = fix_python_syntax(content_escaped)
    elif key_name == "command":
        # Shell command fixing
        content_escaped = fix_shell_syntax(content_escaped)
    else:
        # General JSON fixing
        content_escaped = fix_json_syntax(content_escaped)

    return content_escaped


def fix_python_syntax(content):
    """Fix common syntax errors in Python code"""
    import re

    # Keywords that need to be kept in Python
    # null → None (but be careful not to change null inside strings)
    content = re.sub(r"\bnull\b", "None", content)
    # true → True
    content = re.sub(r"\btrue\b", "True", content)
    # false → False
    content = re.sub(r"\bfalse\b", "False", content)

    # Fix common Python syntax errors
    # e.g.: print "text" → print("text") (Python 2 to 3)
    content = re.sub(r'\bprint\s+"([^"]*)"', r'print("\1")', content)

    return content


def fix_shell_syntax(content):
    """Fix common syntax errors in Shell commands"""
    import re

    # Keyword fixes in Shell
    # true/false are usually lowercase in shell
    content = re.sub(r"\bTrue\b", "true", content)
    content = re.sub(r"\bFalse\b", "false", content)
    content = re.sub(r"\bNone\b", '""', content)  # None is usually empty string in shell

    # Fix common shell syntax issues
    # e.g.: ensure variable references are correct

    return content


def fix_json_syntax(content):
    """Fix common errors in JSON strings"""
    import re

    # JSON standard keyword fixes
    # Python keywords → JSON keywords
    content = re.sub(r"\bNone\b", "null", content)
    content = re.sub(r"\bTrue\b", "true", content)
    content = re.sub(r"\bFalse\b", "false", content)

    return content


def parse_escaped_json_string(raw_str):
    """
    Fix escape issues in JSON strings, supports smart syntax fixing

    Uses 5 progressive parsing strategies:
    1. Direct parsing - return directly if already valid JSON
    2. Line start pattern - use simple line start key pattern for parsing
    3. Negative lookbehind pattern - use complex negative lookbehind to exclude escaped keys
    4. Legacy method - use historically compatible simple string replacement
    5. Conservative fallback - most basic escape fixing

    Args:
        raw_str (str): JSON string that may contain escape issues

    Returns:
        str: Fixed valid JSON string

    Raises:
        json.JSONDecodeError: If all strategies fail to fix into valid JSON
    """
    raw_str = raw_str.strip()

    # Strategy 1: Direct parsing verification
    if _try_direct_parse(raw_str):
        return raw_str

    # Strategy 2: Line start pattern parsing
    result = _try_line_start_pattern(raw_str)
    if result:
        return result

    # Strategy 3: Negative lookbehind pattern parsing
    result = _try_negative_lookbehind_pattern(raw_str)
    if result:
        return result

    # Strategy 4: Legacy method
    result = _try_legacy_method(raw_str)
    if result:
        return result

    # Strategy 5: Conservative fallback
    return _conservative_escape_fallback(raw_str)


def _try_direct_parse(raw_str):
    """Strategy 1: Try direct parsing, no fixing needed if successful"""
    try:
        robust_json_loads(raw_str)
        return True
    except json.JSONDecodeError:
        return False


def _try_line_start_pattern(raw_str):
    """Strategy 2: Use line start pattern for parsing"""
    return _try_parse_with_pattern(raw_str, r'^\s*"([\w\-_]+)"\s*:')


def _try_negative_lookbehind_pattern(raw_str):
    """Strategy 3: Use negative lookbehind pattern for parsing"""
    return _try_parse_with_pattern(raw_str, r'(?<!\\)"([\w\-_]+)"\s*:')


def _try_legacy_method(raw_str):
    """Strategy 4: Try legacy simple method"""
    try:
        corrected_json = _legacy_escape_method(raw_str)
        robust_json_loads(corrected_json)
        return corrected_json
    except (json.JSONDecodeError, Exception):
        return None


def _try_parse_with_pattern(raw_str, pattern):
    """General key pattern-based parsing method"""
    import re

    try:
        # Add multiline flag if it's a line start pattern
        flags = re.MULTILINE if pattern.startswith(r"^\s*") else 0
        key_matches = list(re.finditer(pattern, raw_str, flags))
        if not key_matches:
            return None

        result = []
        last_end = 0

        for i, key_match in enumerate(key_matches):
            key_name = key_match.group(1)
            key_end = key_match.end()

            # Add content before key (including key itself)
            result.append(raw_str[last_end:key_end])

            # Skip whitespace, find the start quote of value
            value_start_pos = key_end
            while value_start_pos < len(raw_str) and raw_str[value_start_pos] in " \t":
                value_start_pos += 1

            if value_start_pos >= len(raw_str) or raw_str[value_start_pos] != '"':
                # Not a string value, skip
                last_end = key_end
                continue

            # Skip the start quote
            value_content_start = value_start_pos + 1

            # Find the end position of value
            if i < len(key_matches) - 1:
                search_limit = key_matches[i + 1].start()
            else:
                search_limit = len(raw_str)

            # Search backwards for value end marker
            value_end_pos = _find_value_end_position(raw_str, value_content_start, search_limit)
            if value_end_pos is None:
                last_end = key_end
                continue

            # Extract and escape value content
            value_content = raw_str[value_content_start:value_end_pos]
            escaped_value = escape_string_content(value_content, key_name)

            # Add fixed value
            result.append(' "')
            result.append(escaped_value)
            result.append('"')

            last_end = value_end_pos + 1

        # Add remaining content
        result.append(raw_str[last_end:])
        corrected_json = "".join(result)

        # Verify fix result
        robust_json_loads(corrected_json)
        return corrected_json

    except (json.JSONDecodeError, re.error, Exception):
        return None


def _find_value_end_position(raw_str, start_pos, search_limit):
    """Find the end position of value"""
    for pos in range(search_limit - 1, start_pos, -1):
        if raw_str[pos] == '"':
            after_quote = raw_str[pos + 1 : search_limit].strip()
            if after_quote.startswith(",") or after_quote.startswith("}") or after_quote == "":
                return pos
    return None


def _legacy_escape_method(raw_str):
    """
    Legacy simple escape method: mainly handles special cases of code_block field
    """
    # Remove leading and trailing whitespace
    raw_str = raw_str.strip()

    # Check if contains code_block field, which needs special handling
    if '"code_block": "' in raw_str:
        # Split into two parts: first part and code content part
        parts = raw_str.split('"code_block": "', 1)
        if len(parts) != 2:
            raise ValueError("Unable to correctly split code_block field")

        # First part: handle escape sequences normally
        first_part = parts[0].replace("\\n", "\n")

        # Second part: code content needs special handling
        second_part = parts[1]

        # Find the end position of code content (should end with "\n})
        if second_part.endswith("\n}"):
            code_content = second_part[:-2]  # Remove the last \n}
        elif second_part.endswith('"\\n}'):
            code_content = second_part[:-4]  # Remove the last "\n}
        else:
            # Find the last " character as code content end
            last_quote = second_part.rfind('"')
            if last_quote == -1:
                raise ValueError("Unable to find end position of code content")
            code_content = second_part[:last_quote]

        # Escape special characters in code content
        # Note the order: escape backslashes first, then quotes, finally handle newlines
        code_content_escaped = (
            code_content.replace("\\", "\\\\")  # Escape backslashes
            .replace('"', '\\"')  # Escape quotes
            .replace("\n", "\\n")
        )  # Keep newlines as escaped format

        # Reassemble the complete JSON string
        corrected_json = first_part + '"code_block": "' + code_content_escaped + '"\n}'

    else:
        # Simple case without code_block, directly replace escape sequences
        corrected_json = raw_str.replace("\\n", "\n").replace("\\\\", "\\")

    return corrected_json


def _escape_for_json(value: str) -> str:
    # Do not escape \" and \uXXXX
    fixed = re.sub(r'(?<!\\)\\(?!["]|u[0-9a-fA-F]{4})', r"\\\\", value)

    # Then escape newlines, order is important: \r\n → \n → \r
    fixed = fixed.replace("\r\n", "\\r\\n").replace("\n", "\\n").replace("\r", "\\r")

    return fixed


def _conservative_escape_fallback(raw_str):
    """
    Conservative fallback strategy: only fix the most obvious issues
    """
    import re

    # Only handle the most common issue: newlines in string values
    def fix_newlines(match):
        key = match.group(1)
        value = match.group(2)

        # Only escape newlines, keep it simple
        fixed_value = _escape_for_json(value)
        return f'"{key}": "{fixed_value}"'

    # Use most conservative regex pattern
    pattern = r'"([^"]+)":\s*"([^"]*)"'

    try:
        return re.sub(pattern, fix_newlines, raw_str)
    except re.error:
        # If even this fails, return original string directly
        return raw_str


def _resolve_native_tool_name(raw_name: str, name_map: dict | None) -> tuple[str, str] | None:
    """Resolve a flat native function name to (server_name, tool_name).

    Uses the lookup table when available; falls back to splitting on '--'.
    Returns None if the name cannot be resolved.
    """
    if name_map and raw_name in name_map:
        return name_map[raw_name]
    if "--" in raw_name:
        return tuple(raw_name.split("--", maxsplit=1))
    return None


def parse_llm_response_for_tool_calls(
    llm_response_content_text, _fix_depth: int = 0, *, name_map: dict | None = None
):
    """
    Parse tool_calls or <use_mcp_tool> tags from LLM response text.
    Returns a list containing tool call information.
    """
    # tool_calls or MCP reponse are handled differently
    # for openai response api, the tool_calls are in the response text
    if isinstance(llm_response_content_text, dict):
        tool_calls = []
        bad_tool_calls = []
        for item in llm_response_content_text.get("output", []):
            if item.get("type") == "function_call":
                raw_name = item.get("name", "")
                resolved = _resolve_native_tool_name(raw_name, name_map)
                if not resolved:
                    logger.warning(f"[Parse] Cannot resolve function_call name: {raw_name}")
                    bad_tool_calls.append({
                        "error": f"Unknown tool '{raw_name}'. Check tool name format.",
                        "id": item.get("call_id", "UNKNOWN"),
                    })
                    continue
                server_name, tool_name = resolved
                arguments_str = item.get("arguments")
                try:
                    # Try to handle possible newlines and escape characters
                    arguments = robust_json_loads(arguments_str)
                except json.JSONDecodeError:
                    logger.debug(f"Warning: Unable to parse tool arguments JSON: {arguments_str}")
                    # Try more lenient parsing or log error
                    try:
                        # Try to replace some common error formats, e.g. Python dict strings
                        arguments_str_fixed = (
                            arguments_str.replace("'", '"')
                            .replace("None", "null")
                            .replace("True", "true")
                            .replace("False", "false")
                        )
                        arguments = robust_json_loads(arguments_str_fixed)
                        logger.debug("Info: Attempted fix and successfully parsed arguments.")
                    except json.JSONDecodeError:
                        logger.debug(
                            f"Error: Still unable to parse tool arguments JSON after fix: {arguments_str}"
                        )
                        arguments = {
                            "error": "Failed to parse arguments",
                            "raw": arguments_str,
                        }
                tool_calls.append(
                    {
                        "server_name": server_name,
                        "tool_name": tool_name,
                        "arguments": arguments,
                        "id": item.get("call_id"),
                    }
                )
        return tool_calls, bad_tool_calls

    # for openai completion api, the tool_calls are in the response text
    if isinstance(llm_response_content_text, list):
        tool_calls = []
        bad_tool_calls = []
        for tool_call in llm_response_content_text:
            raw_name = getattr(tool_call.function, "name", "") or ""
            resolved = _resolve_native_tool_name(raw_name, name_map)
            if not resolved:
                logger.warning(f"[Parse] Cannot resolve native tool name: {raw_name}")
                bad_tool_calls.append({
                    "error": f"Unknown tool '{raw_name}'. Check tool name format (expected 'server--tool').",
                    "id": getattr(tool_call, "id", "UNKNOWN"),
                })
                continue
            server_name, tool_name = resolved
            arguments_str = tool_call.function.arguments

            # Parse JSON string to dictionary
            try:
                # Try to handle possible newlines and escape characters
                arguments = robust_json_loads(arguments_str)
            except json.JSONDecodeError:
                logger.debug(f"Warning: Unable to parse tool arguments JSON: {arguments_str}")
                # Try more lenient parsing or log error
                try:
                    # Try to replace some common error formats, e.g. Python dict strings
                    arguments_str_fixed = (
                        arguments_str.replace("'", '"')
                        .replace("None", "null")
                        .replace("True", "true")
                        .replace("False", "false")
                    )
                    arguments = robust_json_loads(arguments_str_fixed)
                    logger.debug("Info: Attempted fix and successfully parsed arguments.")
                except json.JSONDecodeError:
                    logger.debug(
                        f"Error: Still unable to parse tool arguments JSON after fix: {arguments_str}"
                    )
                    arguments = {
                        "error": "Failed to parse arguments",
                        "raw": arguments_str,
                    }

            tool_calls.append(
                {
                    "server_name": server_name,
                    "tool_name": tool_name,
                    "arguments": arguments,
                    "id": tool_call.id,
                }
            )
        return tool_calls, bad_tool_calls

    # for other clients, such as qwen and anthropic, we use MCP instead of tool calls
    tool_calls = []
    bad_tool_calls = []
    # Find all <use_mcp_tool> tags, using robust regular expressions
    # Allow whitespace, case insensitive, allow tag attributes
    # Use non-greedy captures with explicit delimiters to avoid catastrophic backtracking
    tool_call_patterns = re.findall(
        r"<use_mcp_tool[^>]*>\s*"
        r"<server_name[^>]*>(.*?)</server_name>\s*"
        r"<tool_name[^>]*>(.*?)</tool_name>\s*"
        r"<arguments[^>]*>([\s\S]*?)</arguments>\s*"
        r"</use_mcp_tool>",
        llm_response_content_text,
        re.DOTALL | re.IGNORECASE,
    )

    # Check for invalid tool calls
    # Find all possible incomplete or malformed <use_mcp_tool> tags, using more robust regular expressions
    incomplete_patterns = [
        r"<use_mcp_tool[^>]*?>(?:(?!</use_mcp_tool>).)*?(?:</use_mcp_tool>|$)",  # Complete or incomplete tool calls
        r"<server_name[^>]*?>(?:(?!</server_name>).)*?(?:</server_name>|$)",  # Server name tags
        r"<tool_name[^>]*?>(?:(?!</tool_name>).)*?(?:</tool_name>|$)",  # Tool name tags
        r"<arguments[^>]*?>(?:(?!</arguments>).)*?(?:</arguments>|$)",  # Arguments tags
    ]

    # Check each pattern for incomplete tags
    for pattern in incomplete_patterns:
        matches = re.findall(pattern, llm_response_content_text, re.DOTALL | re.IGNORECASE)
        for match in matches:
            # Check if closing tags are missing (case insensitive)
            if pattern.endswith("</server_name>|$)") and not re.search(
                r"</server_name>\s*$", match, re.IGNORECASE
            ):
                bad_tool_calls.append({"error": "Unclosed server_name tag", "content": match})
            elif pattern.endswith("</tool_name>|$)") and not re.search(
                r"</tool_name>\s*$", match, re.IGNORECASE
            ):
                bad_tool_calls.append({"error": "Unclosed tool_name tag", "content": match})
            elif pattern.endswith("</arguments>|$)") and not re.search(
                r"</arguments>\s*$", match, re.IGNORECASE
            ):
                bad_tool_calls.append({"error": "Unclosed arguments tag", "content": match})
            elif pattern.endswith("</use_mcp_tool>|$)") and not re.search(
                r"</use_mcp_tool>\s*$", match, re.IGNORECASE
            ):
                bad_tool_calls.append({"error": "Unclosed use_mcp_tool tag", "content": match})

    # If invalid tool calls are found, log warnings
    if bad_tool_calls:
        logger.debug(f"Warning: Found {len(bad_tool_calls)} invalid tool calls")
        for bad_call in bad_tool_calls:
            logger.debug(f"Invalid tool call: {bad_call['error']} - {bad_call['content'][:100]}...")

    for match in tool_call_patterns:
        server_name = match[0].strip()
        tool_name = match[1].strip()
        arguments_str = match[2].strip()

        # Parse JSON string to dictionary
        try:
            # Try to handle possible newlines and escape characters
            arguments = robust_json_loads(arguments_str)
        except json.JSONDecodeError:
            logger.debug(f"Warning: Unable to parse tool arguments JSON: {arguments_str}")
            # Try more lenient parsing or log error
            try:
                # Uniformly use smart JSON fixing, no longer special handling for specific tools
                arguments_str_fixed = parse_escaped_json_string(arguments_str)
                arguments = robust_json_loads(arguments_str_fixed)
                logger.debug("Info: Attempted fix and successfully parsed arguments.")
            except json.JSONDecodeError:
                logger.debug(
                    f"Error: Still unable to parse tool arguments JSON after fix: {arguments_str}"
                )
                arguments = {"error": "Failed to parse arguments", "raw": arguments_str}

        tool_calls.append(
            {
                "server_name": server_name,
                "tool_name": tool_name,
                "arguments": arguments,
                "id": None,
            }
        )

    for item in bad_tool_calls:
        if item["error"] == "Unclosed arguments tag" and _fix_depth < 1:
            # Try to fix missing </arguments> case (depth-limited to prevent infinite recursion)
            content = llm_response_content_text
            if content.find("<arguments>") != -1 and content.find("</arguments>") == -1:
                # Find <arguments> start position
                args_start = content.find("<arguments>") + len("<arguments>")
                # Find next </ tag as end position
                next_tag = content.find("</", args_start)
                if next_tag != -1:
                    # Add </arguments> before next tag
                    fixed_content = content[:next_tag] + "</arguments>" + content[next_tag:]
                else:
                    # If no next tag, add </arguments> at the end
                    fixed_content = content + "</arguments>"

                logger.info("Attempting to fix tool call with missing </arguments>, re-parsing...")
                # Re-parse fixed content (non-recursive to prevent infinite loops)
                return parse_llm_response_for_tool_calls(fixed_content, _fix_depth=1)

    return tool_calls, bad_tool_calls
