"""
cortex/core/retrieval_helpers.py — Content rendering, object accessors, and utilities.

Extracted from cortex/core/retrieval.py (#947 — SRP split).
All functions here are pure helpers with no I/O or state.
"""

from __future__ import annotations

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


# ---------------------------------------------------------------------------
# Claim content rendering (LoCoMo v10 fields)
# ---------------------------------------------------------------------------

def _render_claim_content(claim: Any) -> str:
    """Build enriched content string from a SemanticClaim, surfacing LoCoMo v10 fields.

    Format:
        [event_date] subject predicate object [status] (because: cause → effect) (was: before → now: after)

    Rules:
        - event_date: prefix with [date] if present
        - status: append [status] only if NOT 'active'
        - cause + effect: append (because: cause → effect) if both; single form if only one
        - state_before + state_after: append (was: X → now: Y) if both; single form if only one
    """
    event_date = getattr(claim, "event_date", None)
    status = getattr(claim, "status", "active") or "active"
    cause = getattr(claim, "cause", None)
    effect = getattr(claim, "effect", None)
    state_before = getattr(claim, "state_before", None)
    state_after = getattr(claim, "state_after", None)

    prefix = f"[{event_date}] " if event_date else ""
    core = f"{claim.subject} {claim.predicate} {claim.object_value}"

    suffixes: List[str] = []

    if status and status != "active":
        suffixes.append(f"[{status}]")

    if cause and effect:
        suffixes.append(f"(because: {cause} \u2192 {effect})")
    elif cause:
        suffixes.append(f"(cause: {cause})")
    elif effect:
        suffixes.append(f"(effect: {effect})")

    if state_before and state_after:
        suffixes.append(f"(was: {state_before} \u2192 now: {state_after})")
    elif state_before:
        suffixes.append(f"(was: {state_before})")
    elif state_after:
        suffixes.append(f"(now: {state_after})")

    result = prefix + core
    if suffixes:
        result += " " + " ".join(suffixes)

    # Append episode linkage so agents can optionally pull the source evidence (#919)
    episode_event_id = getattr(claim, "episode_event_id", None)
    if episode_event_id:
        result += f" [episode:{episode_event_id}]"

    return result


# ---------------------------------------------------------------------------
# Utilities
# ---------------------------------------------------------------------------

def _new_id() -> str:
    return uuid.uuid4().hex


def _table_to_item_type(table: str) -> str:
    return {"claims": "claim", "episodes": "event"}.get(table, table)


# The following helpers handle both dataclass objects and plain dicts/namedtuples
# returned by different StorageAdapter implementations.

def _get_id(obj: Any) -> str:
    if hasattr(obj, "item_id"):
        return obj.item_id
    if hasattr(obj, "claim_id"):
        return obj.claim_id
    if hasattr(obj, "event_id"):
        return obj.event_id
    if isinstance(obj, dict):
        return obj.get("item_id") or obj.get("claim_id") or obj.get("event_id") or ""
    return str(getattr(obj, "id", ""))


def _get_content(obj: Any) -> str:
    if hasattr(obj, "content"):
        return obj.content
    if isinstance(obj, dict):
        return obj.get("content", "")
    return ""


def _get_rank(obj: Any) -> float:
    for attr in ("rank", "bm25_rank", "score"):
        if hasattr(obj, attr):
            return float(getattr(obj, attr))
    if isinstance(obj, dict):
        return float(obj.get("rank", obj.get("score", 0.0)))
    return 0.0


def _get_score(obj: Any) -> float:
    if hasattr(obj, "score"):
        return float(obj.score)
    if isinstance(obj, dict):
        return float(obj.get("score", 0.0))
    return 0.0


def _get_item_type(obj: Any) -> str:
    if hasattr(obj, "item_type"):
        return obj.item_type
    if isinstance(obj, dict):
        return obj.get("item_type", "claim")
    return "claim"


def _decode_embedding(row: Any) -> Optional[List[float]]:
    """Decode a stored embedding blob or list back to a Python float list."""
    raw = getattr(row, "embedding", None) or (
        row.get("embedding") if isinstance(row, dict) else None
    )
    if raw is None:
        return None
    if isinstance(raw, (list, tuple)):
        return [float(x) for x in raw]
    if isinstance(raw, (bytes, bytearray)):
        import struct
        n = len(raw) // 4
        return list(struct.unpack(f"{n}f", raw[:n * 4]))
    return None
