"""json_utils.py — Shared JSON extraction from LLM responses.

Used by: commander_client, deepseek_agent, market_interpreter, self_healer.
Single implementation eliminates 4 duplicate, divergent extractors.
"""

from __future__ import annotations

import json
import re
from typing import Any


def extract_json_block(text: str) -> dict[str, Any]:
    """Robustly extract a JSON object from an LLM response string.

    Attempts (in priority order):
    1. Direct parse (pure JSON response)
    2. Strip markdown ```json ... ``` fences
    3. Find first { to last } span
    4. Trailing-comma sanitisation retry on each candidate

    Raises:
        ValueError: if no valid JSON object could be parsed.
    """
    if not text or not text.strip():
        raise ValueError("Empty response text")

    raw = text.strip()
    candidates: list[str] = []

    # Candidate 1: direct parse
    candidates.append(raw)

    # Candidate 2: strip markdown ```json or ``` fences
    m = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', raw, re.DOTALL)
    if m:
        candidates.append(m.group(1).strip())

    # Candidate 3: find outermost { ... }
    start = raw.find('{')
    end = raw.rfind('}')
    if start != -1 and end != -1 and end > start:
        candidates.append(raw[start:end + 1].strip())

    for candidate in candidates:
        if not candidate:
            continue
        try:
            return json.loads(candidate)
        except json.JSONDecodeError:
            # Common LLM mistake: trailing comma before closing brace
            try:
                cleaned = candidate.replace(",\n}", "\n}").replace(", }", "}")
                return json.loads(cleaned)
            except json.JSONDecodeError:
                continue

    preview = text[:300].replace('\n', ' ').strip()
    raise ValueError(f"No JSON found in response: {preview}")


# Backward-compatible alias
_extract_json = extract_json_block
