"""Bootstrap reconciliation helpers for Hippo.

Port of the TypeScript ``isBootstrapMessage``, ``readLeafPathMessages``,
and ``messageIdentity`` helpers from ``engine.ts`` into sync Python.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any


def is_bootstrap_message(value: Any) -> bool:
    """Return *True* if *value* looks like a valid agent message dict.

    A valid message must be a ``dict`` with a ``role`` key whose value is a
    string, **and** either a ``content`` key or both ``command`` and ``output``
    keys.
    """
    if not isinstance(value, dict):
        return False
    if not isinstance(value.get("role"), str):
        return False
    return "content" in value or ("command" in value and "output" in value)


def read_leaf_path_messages(session_file: str | Path) -> list[dict]:
    """Read a JSON or JSONL session file and return valid bootstrap messages.

    Behaviour mirrors the TypeScript ``readLeafPathMessages``:

    1. Try to read the file; return ``[]`` on any I/O error.
    2. If the trimmed content starts with ``[``, parse as a JSON array and
       filter through :func:`is_bootstrap_message`.
    3. Otherwise treat each line as JSONL.  Each line may be a direct message
       object **or** a ``{"message": <msg>}`` wrapper – both forms are handled.
    4. Malformed lines are silently skipped.
    """
    try:
        raw = Path(session_file).read_text(encoding="utf-8")
    except OSError:
        return []

    trimmed = raw.strip()
    if not trimmed:
        return []

    # --- JSON-array mode ---
    if trimmed.startswith("["):
        try:
            parsed = json.loads(trimmed)
        except (json.JSONDecodeError, ValueError):
            return []
        if not isinstance(parsed, list):
            return []
        return [m for m in parsed if is_bootstrap_message(m)]

    # --- JSONL mode ---
    messages: list[dict] = []
    for line in raw.split("\n"):
        item = line.strip()
        if not item:
            continue
        try:
            parsed = json.loads(item)
        except (json.JSONDecodeError, ValueError):
            continue
        # Unwrap optional {message: ...} envelope
        if isinstance(parsed, dict) and "message" in parsed:
            candidate = parsed["message"]
        else:
            candidate = parsed
        if is_bootstrap_message(candidate):
            messages.append(candidate)
    return messages


def message_identity(role: str, content: str) -> str:
    """Return a dedup key for a message: ``role\\0content``."""
    return f"{role}\x00{content}"


def flatten_content(msg: dict) -> str:
    """Extract readable text from a message dict.

    Includes tool-call/tool-result markers for logging/debugging visibility.
    Unlike _extract_message_content (which strips non-text for DB storage),
    this preserves structured block types as human-readable placeholders.
    """
    content = msg.get("content", "")
    if isinstance(content, str):
        return content
    if not isinstance(content, list):
        return str(content) if content else ""
    parts = []
    for block in content:
        if not isinstance(block, dict):
            parts.append(str(block))
            continue
        btype = block.get("type", "")
        if btype == "text":
            text = block.get("text", "")
            if text:
                parts.append(text)
        elif btype in ("tool_use", "toolUse", "tool-use", "functionCall", "function_call"):
            name = block.get("name") or block.get("toolName") or "unknown"
            parts.append(f"[tool_call: {name}]")
        elif btype in ("tool_result", "toolResult"):
            result_content = block.get("content", "")
            if isinstance(result_content, str):
                parts.append(result_content)
            elif isinstance(result_content, list):
                for sub in result_content:
                    if isinstance(sub, dict) and sub.get("type") == "text":
                        parts.append(sub.get("text", ""))
            else:
                parts.append(str(result_content))
        elif btype in ("thinking", "reasoning"):
            parts.append("[thinking]")
    return "\n".join(parts) if parts else ""


def extract_message_parts(msg: dict) -> list[dict]:
    """Extract structured parts from a message dict.

    K-005 contract: returns [] for plain text content, only decomposes
    structured (list) content into parts. Special case: role="tool" messages
    (OpenAI format) are extracted as tool_result parts even with string content.
    For full part-writing (including text parts), use engine._build_message_parts().
    """
    import json as _json
    role = msg.get("role", "user")
    content = msg.get("content", "")

    # OpenAI tool result: role="tool" with string content + tool_call_id
    if role == "tool" and isinstance(content, str):
        tool_call_id = msg.get("tool_call_id")
        if tool_call_id:
            return [{
                "ordinal": 0,
                "part_type": "tool_result",
                "text_content": None,
                "tool_call_id": str(tool_call_id),
                "tool_name": msg.get("name"),
                "tool_output": content,
                "metadata": _json.dumps({"originalRole": role}),
            }]
        return []

    # OpenAI assistant with tool_calls: decompose text + each tool_call
    tool_calls = msg.get("tool_calls")
    if tool_calls and isinstance(tool_calls, list):
        parts = []
        ordinal = 0
        # Text part if present
        if isinstance(content, str) and content:
            parts.append({
                "ordinal": ordinal,
                "part_type": "text",
                "text_content": content,
                "metadata": _json.dumps({"originalRole": role}),
            })
            ordinal += 1
        # Tool call parts
        for tc in tool_calls:
            func = tc.get("function", {})
            parts.append({
                "ordinal": ordinal,
                "part_type": "tool_use",
                "text_content": None,
                "tool_call_id": str(tc.get("id", "")),
                "tool_name": func.get("name"),
                "tool_input": func.get("arguments"),
                "metadata": _json.dumps({"originalRole": role, "rawType": "function_call"}),
            })
            ordinal += 1
        return parts

    if isinstance(content, str):
        return []
    from cortex.hippo.engine import _build_message_parts
    return _build_message_parts(role, content)
