//! Mutable execution context handed to every operation body.
//!
//! Phase 1 carries the minimum surface: actor id, signing key, and the
//! authoritative commit layer. Future iterations layer in oplog access,
//! sync backend, blob backend, and per-op session state.

use std::path::PathBuf;

use ed25519_dalek::SigningKey;
use ontoref_commit::BinaryMerkle;
use ontoref_ontology::types::StateConfig;

/// Per-invocation execution context. Borrowed mutably so ops can append
/// effects to the substrate's commit layer and emit witnesses.
pub struct OpContext {
    /// Id of the actor invoking the operation. Recorded on the witness.
    pub actor_id: String,
    /// Operation id (matches [`crate::OperationEntry::id`]). Set by
    /// dispatch before the op body runs so validators / effect emitters
    /// can reference it.
    pub op_id: String,
    /// Ed25519 signing key. Used by the runtime to sign the witness
    /// payload at the end of the pipeline.
    pub signing_key: SigningKey,
    /// Authoritative commit layer this op may mutate.
    pub commit_layer: BinaryMerkle,
    /// FSM state (state.ncl). Loaded by domains that need it (e.g.
    /// `move_fsm_state` in O3). `None` when the op does not touch FSM
    /// state — keeps the context cheap for noop and meta ops.
    pub state: Option<StateConfig>,
    /// Core ontology snapshot (`core.ncl`). Loaded by ops that mutate
    /// nodes or edges in the core DAG (e.g. `update_ontology_node` in O4).
    pub core: Option<serde_json::Value>,
    /// Backlog snapshot (`reflection/backlog.ncl`). Loaded by
    /// `manage_backlog_item` and similar ops.
    pub backlog: Option<serde_json::Value>,
    /// Project root used to resolve relative `render_paths`. When
    /// `None`, the runtime skips render and only emits a witness over
    /// the in-memory state.
    pub project_root: Option<PathBuf>,
    /// Logical counter for synthesising HLC timestamps inside op
    /// bodies. The runtime hands out monotonic values via
    /// [`Self::next_hlc`].
    pub hlc_counter: u64,
    /// Oplog the runtime appends the signed witness to as the final
    /// pipeline stage (`substrate` feature, ADR-036). `None` keeps the
    /// pipeline in-memory — the witness is signed and returned but not
    /// persisted, matching tier-0/1 behaviour.
    #[cfg(feature = "substrate")]
    pub oplog: Option<std::sync::Arc<ontoref_oplog::OpLog>>,
}

impl OpContext {
    /// Build a context with a fresh empty commit layer — convenient for
    /// tests and the noop op path.
    pub fn new_in_memory(
        actor_id: impl Into<String>,
        op_id: impl Into<String>,
        signing_key: SigningKey,
    ) -> Self {
        Self {
            actor_id: actor_id.into(),
            op_id: op_id.into(),
            signing_key,
            commit_layer: BinaryMerkle::new(),
            state: None,
            core: None,
            backlog: None,
            project_root: None,
            hlc_counter: 0,
            #[cfg(feature = "substrate")]
            oplog: None,
        }
    }

    /// Attach an oplog so the runtime persists the signed witness as the
    /// final pipeline stage (`substrate` feature, ADR-036). The handle is
    /// shared (`Arc`) because the daemon owns one long-lived log across
    /// many op invocations.
    #[cfg(feature = "substrate")]
    pub fn with_oplog(mut self, oplog: std::sync::Arc<ontoref_oplog::OpLog>) -> Self {
        self.oplog = Some(oplog);
        self
    }

    /// Attach an FSM state — used by ops that mutate `.ontology/state.ncl`.
    pub fn with_state(mut self, state: StateConfig) -> Self {
        self.state = Some(state);
        self
    }

    /// Attach a core ontology snapshot — used by ops that mutate
    /// `.ontology/core.ncl`.
    pub fn with_core(mut self, core: serde_json::Value) -> Self {
        self.core = Some(core);
        self
    }

    /// Attach a backlog snapshot — used by `manage_backlog_item`.
    pub fn with_backlog(mut self, backlog: serde_json::Value) -> Self {
        self.backlog = Some(backlog);
        self
    }

    /// Attach a project root so the runtime can resolve relative
    /// `render_paths`.
    pub fn with_project_root(mut self, root: PathBuf) -> Self {
        self.project_root = Some(root);
        self
    }

    /// Hand out a monotonically increasing HLC timestamp for ops that
    /// need to append to the commit layer.
    pub fn next_hlc(&mut self) -> ontoref_types::Hlc {
        self.hlc_counter += 1;
        ontoref_types::Hlc::new(self.hlc_counter, 0)
    }
}
