//! `evaluate_ondaod` — meta-op recording an ondaod evaluation.
//!
//! No state mutation outside the commit layer; no render paths. The op
//! emits an `OndaodEvaluation` entity carrying the engaged tensions
//! and synthesis-state characterisation the caller supplied. Used as a
//! reusable precondition by `propose_adr` (O4) and `transition_adr` (O6).

use ontoref_derive::onto_operation;
use ontoref_types::canonical;
use ontoref_types::operation::{OpBody, OpPayload};
use ontoref_types::{AttrId, EntityId, Operation, Value};
use smol_str::SmolStr;

use crate::context::OpContext;
use crate::error::OpError;
use crate::output::{EffectRecord, OpOutput};

/// Inputs: `{ topic: String, engaged_tensions: [String], synthesis_state:
/// String, motion_direction: String }`.
#[onto_operation(
    id = "evaluate_ondaod",
    description = "Record an ondaod (Dao Discipline) evaluation as a meta-decision entity. No \
                   state mutation, no render.",
    validation_sla = "Synchronous",
    precondition = "ondaod",
    pre_validators = "",
    effects = "Insert:OndaodEvaluation:topic,engaged_tensions,synthesis_state,motion_direction",
    render_paths = "",
    witness_shape = "ondaod_evaluation",
    actor_policy = "developer,admin,agent"
)]
fn evaluate_ondaod(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result<OpOutput, OpError> {
    let topic = inputs
        .get("topic")
        .and_then(|v| v.as_str())
        .ok_or_else(|| OpError::body("evaluate_ondaod", "missing 'topic' input"))?
        .to_owned();
    let engaged = inputs
        .get("engaged_tensions")
        .map(|v| v.to_string())
        .unwrap_or_else(|| "[]".to_owned());
    let synthesis = inputs
        .get("synthesis_state")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_owned();
    let motion = inputs
        .get("motion_direction")
        .and_then(|v| v.as_str())
        .unwrap_or("Static")
        .to_owned();

    let entity_id = format!("ondaod_eval:{}", blake3::hash(topic.as_bytes()).to_hex());
    let signing_key = ctx.signing_key.clone();
    let actor = ontoref_types::PublicKey::from_signing_key(&signing_key);
    let timestamp = ctx.next_hlc();
    let op = Operation::sign(
        OpBody {
            actor,
            parents: vec![],
            payload: OpPayload::EntityAssert {
                attrs: vec![
                    (AttrId::new("topic"), Value::Str(SmolStr::new(&topic))),
                    (
                        AttrId::new("engaged_tensions"),
                        Value::Str(SmolStr::new(&engaged)),
                    ),
                    (
                        AttrId::new("synthesis_state"),
                        Value::Str(SmolStr::new(&synthesis)),
                    ),
                    (
                        AttrId::new("motion_direction"),
                        Value::Str(SmolStr::new(&motion)),
                    ),
                ],
                entity: EntityId::new(&entity_id),
                valid_from: None,
            },
            timestamp,
        },
        &signing_key,
    );
    ctx.commit_layer.apply(&op);

    let witness_payload =
        canonical::encode(&op).map_err(|e| OpError::body("evaluate_ondaod", e.to_string()))?;
    Ok(OpOutput {
        effects: vec![EffectRecord {
            kind: "Insert".to_owned(),
            entity: entity_id,
            attrs: vec![
                "topic".into(),
                "engaged_tensions".into(),
                "synthesis_state".into(),
                "motion_direction".into(),
            ],
            dimensions: vec![],
        }],
        witness_payload,
    })
}
