#
#   Imandra Inc.
#
#   speclogician/logic/utils.py
#

from __future__ import annotations

from typing import Any, Literal

InstanceStatus = Literal["sat", "unsat", "unknown", "err"]

_ONEOF_CANDIDATES = ("result", "res", "outcome", "status", "response")

def instance_status(res: Any) -> InstanceStatus:

    # 1) Prefer oneof introspection if available
    for oneof_name in _ONEOF_CANDIDATES:
        try:
            which = res.WhichOneof(oneof_name)
        except Exception:
            continue
        if which in ("sat", "unsat", "unknown", "err"):
            return which  # type: ignore[return-value]

    # 2) Fallback to HasField checks
    for k in ("sat", "unsat", "unknown", "err"):
        try:
            if res.HasField(k):
                return k  # type: ignore[return-value]
        except Exception:
            pass

    # 3) Conservative fallback
    return "unknown"


def instance_is_sat(res: Any) -> bool:
    return res.sat is not None

def instance_model(res: Any) -> Any | None:
    """
    Returns Model if SAT, else None.
    """
    return res.sat.model.src if res.sat is not None and res.sat.model is not None else None 

def instance_model_src(res: Any) -> str | None:
    """
    Returns Model.src if SAT, else None.
    """
    m = instance_model(res)
    if m is None:
        return None
    return getattr(m, "src", None)


def instance_model_artifact(res: Any) -> Any | None:
    """
    Returns Model.artifact if SAT, else None.
    """
    m = instance_model(res)
    if m is None:
        return None
    return getattr(m, "artifact", None)


def instance_unsat_proof(res: Any) -> str | None:
    """
    Returns Unsat.proof_pp if UNSAT, else None.
    """
    if instance_status(res) != "unsat":
        return None
    try:
        return res.unsat.proof_pp
    except Exception:
        return None


def instance_unknown_reason(res: Any) -> str | None:
    """
    Returns unknown StringMsg content if available.
    `_utils_pb2.StringMsg` typically stores a `msg` or `value` field; handle both.
    """
    if instance_status(res) != "unknown":
        return None
    u = getattr(res, "unknown", None)
    if u is None:
        return None
    return getattr(u, "msg", None) or getattr(u, "value", None) or str(u)


def instance_has_errors(res: Any) -> bool:
    try:
        return len(res.errors) > 0
    except Exception:
        return False


def instance_to_jsonable(res: Any) -> dict[str, Any]:
    """
    Minimal, stable JSONable view for SpecLogician logs/CLI:
      - status
      - model.src (if sat)
      - unsat.proof_pp (if unsat)
      - unknown reason (if unknown)
      - errors count (+ optionally stringified errors)
      - task (stringified)
    """
    st = instance_status(res)
    out: dict[str, Any] = {
        "status": st,
        "has_errors": instance_has_errors(res),
        "errors_count": len(res.errors) if hasattr(res, "errors") else 0,
    }

    if st == "sat":
        m = res.sat.model
        out["model"] = {
            "m_type": str(getattr(m, "m_type", "")),
            "src": getattr(m, "src", ""),
            # artifact can be huge; include a string repr unless you want deeper extraction
            "artifact": str(getattr(m, "artifact", "")),
        }
    elif st == "unsat":
        out["proof_pp"] = getattr(res.unsat, "proof_pp", "")
    elif st == "unknown":
        out["reason"] = instance_unknown_reason(res)
    elif st == "err":
        out["reason"] = "err"

    # Task is protobuf; stringify safely
    if hasattr(res, "task"):
        out["task"] = str(res.task)

    return out
