use runx_contracts::{ClosureDisposition, JsonValue};
use thiserror::Error;

#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub(crate) enum ClosureDispositionParseError {
    #[error("agent answer closure must be an object")]
    ClosureNotObject,
    #[error("agent answer closure.disposition is required")]
    MissingDisposition,
    #[error("agent answer closure.disposition must be a string")]
    DispositionNotString,
    #[error("agent answer closure.disposition {0:?} is not supported")]
    UnsupportedDisposition(String),
}

pub(crate) fn parse_agent_answer_disposition(
    answer: &JsonValue,
) -> Result<ClosureDisposition, ClosureDispositionParseError> {
    let closure = answer
        .as_object()
        .and_then(|object| object.get("closure"))
        .ok_or(ClosureDispositionParseError::MissingDisposition)?;
    let closure = closure
        .as_object()
        .ok_or(ClosureDispositionParseError::ClosureNotObject)?;
    let disposition = closure
        .get("disposition")
        .ok_or(ClosureDispositionParseError::MissingDisposition)?;
    let disposition = disposition
        .as_str()
        .ok_or(ClosureDispositionParseError::DispositionNotString)?;
    parse_closure_disposition(disposition)
}

pub(crate) fn agent_answer_disposition_or_closed(
    answer: &JsonValue,
) -> Result<ClosureDisposition, ClosureDispositionParseError> {
    match answer.as_object() {
        Some(object) if object.contains_key("closure") => parse_agent_answer_disposition(answer),
        _ => Ok(ClosureDisposition::Closed),
    }
}

pub(crate) fn parse_closure_disposition(
    disposition: &str,
) -> Result<ClosureDisposition, ClosureDispositionParseError> {
    match disposition {
        "closed" => Ok(ClosureDisposition::Closed),
        "deferred" => Ok(ClosureDisposition::Deferred),
        "superseded" => Ok(ClosureDisposition::Superseded),
        "declined" => Ok(ClosureDisposition::Declined),
        "blocked" => Ok(ClosureDisposition::Blocked),
        "failed" => Ok(ClosureDisposition::Failed),
        "killed" => Ok(ClosureDisposition::Killed),
        "timed_out" => Ok(ClosureDisposition::TimedOut),
        other => Err(ClosureDispositionParseError::UnsupportedDisposition(
            other.to_owned(),
        )),
    }
}
