//! `start_dev_session` — open a code-development session with declared scope.
//!
//! Records the session's scope, target paths, and ADRs applicable to
//! the work as an EntityAssert. The matching `close_dev_session` op
//! mutates `status=Closed` + records any drift observed.

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};

#[onto_operation(
    id = "start_dev_session",
    description = "Open a code dev session with declared scope and target paths. Records session \
                   id, scope, and applicable ADRs.",
    validation_sla = "Synchronous",
    precondition = "",
    pre_validators = "",
    effects = "Insert:DevSession:scope,target,status",
    render_paths = "",
    witness_shape = "dev_session_start",
    actor_policy = "developer,admin"
)]
fn start_dev_session(inputs: &serde_json::Value, ctx: &mut OpContext) -> Result<OpOutput, OpError> {
    let scope = inputs
        .get("scope")
        .and_then(|v| v.as_str())
        .ok_or_else(|| OpError::body("start_dev_session", "missing 'scope'"))?
        .to_owned();
    let target = inputs
        .get("target")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_owned();
    let session_id = format!(
        "dev_session:{}",
        blake3::hash(format!("{scope}|{target}|{}", ctx.hlc_counter).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("scope"), Value::Str(SmolStr::new(&scope))),
                    (AttrId::new("target"), Value::Str(SmolStr::new(&target))),
                    (AttrId::new("status"), Value::Str(SmolStr::new("Open"))),
                ],
                entity: EntityId::new(&session_id),
                valid_from: None,
            },
            timestamp,
        },
        &signing_key,
    );
    ctx.commit_layer.apply(&op);

    let witness_payload =
        canonical::encode(&op).map_err(|e| OpError::body("start_dev_session", e.to_string()))?;
    Ok(OpOutput {
        effects: vec![EffectRecord {
            kind: "Insert".to_owned(),
            entity: session_id,
            attrs: vec!["scope".into(), "target".into(), "status".into()],
            dimensions: vec![],
        }],
        witness_payload,
    })
}
