//! Shared instruction operand decoding helpers used by both ProtocolMachine executors.

use crate::coroutine::Fault;
use crate::coroutine::{Coroutine, Value};
use crate::instr::Endpoint;

// RECURSION_SAFE: structural recursion over a finite value tree.
fn val_type_of(value: &Value) -> telltale_types::ValType {
    use telltale_types::ValType;
    match value {
        Value::Unit => ValType::Unit,
        Value::Nat(_) => ValType::Nat,
        Value::Bool(_) => ValType::Bool,
        Value::Str(_) => ValType::String,
        Value::Prod(left, right) => {
            ValType::Prod(Box::new(val_type_of(left)), Box::new(val_type_of(right)))
        }
        Value::Endpoint(ep) => ValType::Chan {
            sid: ep.sid,
            role: ep.role.clone(),
        },
    }
}

/// Decode an endpoint operand register for channel/session instructions.
///
/// # Errors
///
/// Returns a `Fault::OutOfRegisters` if the register is out-of-bounds, or
/// `Fault::TypeViolation` if the register is not an endpoint value.
pub fn endpoint_from_reg(coro: &Coroutine, reg: u16) -> Result<Endpoint, Fault> {
    match coro.regs.get(usize::from(reg)).cloned() {
        Some(Value::Endpoint(ep)) => Ok(ep),
        Some(other) => Err(Fault::TypeViolation {
            expected: telltale_types::ValType::Chan {
                sid: 0,
                role: String::new(),
            },
            actual: val_type_of(&other),
            message: "expected endpoint register".to_string(),
        }),
        None => Err(Fault::OutOfRegisters),
    }
}

/// Decode a `(endpoint, string)` product used by epistemic tag/check ops.
#[must_use]
pub fn decode_endpoint_fact(value: Value) -> Option<(Endpoint, String)> {
    match value {
        Value::Prod(left, right) => match (*left, *right) {
            (Value::Endpoint(endpoint), Value::Str(fact)) => Some((endpoint, fact)),
            _ => None,
        },
        _ => None,
    }
}

/// Decode a branch label payload for choose/offer dispatch.
///
/// # Errors
///
/// Returns `Fault::TypeViolation` when the payload is not a string label.
pub fn decode_branch_label_payload(role: &str, payload: &Value) -> Result<String, Fault> {
    match payload {
        Value::Str(label) => Ok(label.clone()),
        other => Err(Fault::TypeViolation {
            expected: telltale_types::ValType::String,
            actual: val_type_of(other),
            message: format!("{role}: choose expects string branch label payload"),
        }),
    }
}
