//! Cross-project cell-witness fetch (ADR-028 / D22, D28).
//!
//! [`fetch_cell_witness`] is the load-bearing operation: a project
//! invoking an op whose precondition reaches into ontology `X` calls
//! this function with `X`'s [`OntologyId`] and a [`CellQuery`]; the
//! return value is **only** the witness plus the state root. The
//! foreign ontology's full state, full oplog, or commit-layer cell map
//! are NEVER materialised in the caller's address space.
//!
//! Phase 1 (D28) resolves `OntologyId → local_path` via
//! [`crate::registry::OntologyRegistry`] and reads the witness from
//! a local oplog rebuilt into a [`ontoref_commit::BinaryMerkle`]. The
//! rebuild happens behind the function boundary and the rebuilt commit
//! layer is dropped before the response leaves the function — the
//! caller cannot get a handle on it.

use std::path::PathBuf;

use ontoref_commit::{BinaryMerkle, StateRoot, Witness};
use ontoref_oplog::OpLog;
use ontoref_types::{AttrId, EntityId, Operation};
use serde::{Deserialize, Serialize};

use crate::classification::OntologyType;
use crate::ontology_id::OntologyId;
use crate::registry::{OntologyRegistry, RegistryEntry};
use crate::{Error, Result};

/// A query identifying one cell in an ontology's commit layer.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CellQuery {
    /// Entity id the cell belongs to.
    pub entity: EntityId,
    /// Attribute id of the cell.
    pub attr: AttrId,
}

impl CellQuery {
    /// Build a query from string ids.
    pub fn new(entity: impl AsRef<str>, attr: impl AsRef<str>) -> Self {
        Self {
            entity: EntityId::new(entity.as_ref()),
            attr: AttrId::new(attr.as_ref()),
        }
    }
}

/// Result of a cell-witness fetch — the minimum surface needed to
/// verify the cell against the foreign ontology's state root.
///
/// This struct is **deliberately narrow**: it does NOT carry the full
/// oplog, the full commit layer, the full cell map, or any other
/// surface that could be used to reconstruct the foreign state. The
/// constraint is enforced at the type level — adding a field that
/// widens the surface is a visible change.
#[derive(Debug, Clone)]
pub struct CellWitnessResponse {
    /// Inclusion proof for the requested cell.
    pub witness: Witness,
    /// State root of the foreign ontology at the moment the witness
    /// was built. The witness verifies against this root.
    pub state_root: StateRoot,
}

/// Metadata returned by [`fetch_ontology`] — describes a foreign
/// ontology without revealing its content.
#[derive(Debug, Clone)]
pub struct OntologyHandle {
    /// Content address of the ontology.
    pub ontology_id: OntologyId,
    /// Classification controlling sharing semantics.
    pub classification: OntologyType,
    /// State root at the time the handle was created.
    pub state_root: StateRoot,
}

/// Resolve `id` against the local registry, rebuild its commit layer
/// from the local oplog, and return only a metadata handle. The handle
/// includes the state root but **not** the cell map.
///
/// # Errors
///
/// - [`Error::UnknownOntology`] when `id` is not registered locally.
/// - [`Error::OpenOplog`] when the oplog file cannot be opened.
/// - [`Error::Append`] when the oplog returns an internal error while replaying
///   operations.
pub fn fetch_ontology(registry: &OntologyRegistry, id: &OntologyId) -> Result<OntologyHandle> {
    let entry = resolve(registry, id)?;
    let state_root = compute_state_root(&entry.oplog_path)?;
    Ok(OntologyHandle {
        ontology_id: *id,
        classification: entry.classification,
        state_root,
    })
}

/// Fetch a single cell witness from the foreign ontology identified by
/// `id`. Returns only the witness + state root — the foreign ontology's
/// full state is **never** materialised in the caller's namespace.
///
/// Phase 1 reads from the local oplog mapped by the registry. Once a
/// real `SyncBackend` lands (post trigger, ADR-027), this function will
/// gain an optional `sync_backend: &dyn SyncBackend` parameter that
/// falls back to remote fetch when the ontology id is unknown locally.
///
/// # Errors
///
/// - [`Error::UnknownOntology`] when `id` is not registered locally.
/// - [`Error::OpenOplog`] when the oplog file cannot be opened.
/// - [`Error::CellMissing`] when the cell `(entity, attr)` has never been
///   asserted under the ontology's commit layer.
/// - [`Error::Append`] when the oplog returns an internal error while replaying
///   operations.
pub fn fetch_cell_witness(
    registry: &OntologyRegistry,
    id: &OntologyId,
    query: &CellQuery,
) -> Result<CellWitnessResponse> {
    let entry = resolve(registry, id)?;
    let commit = rebuild_commit_layer(&entry.oplog_path)?;
    let state_root = commit.root();
    let witness = commit.witness(&query.entity, &query.attr)?;

    // Drop the commit layer explicitly before returning. The caller
    // cannot reach into the full state map — only the witness + root
    // cross the function boundary.
    drop(commit);

    Ok(CellWitnessResponse {
        witness,
        state_root,
    })
}

fn resolve<'r>(registry: &'r OntologyRegistry, id: &OntologyId) -> Result<&'r RegistryEntry> {
    registry
        .lookup(id)
        .ok_or_else(|| Error::UnknownOntology(id.to_hex()))
}

fn rebuild_commit_layer(oplog_path: &PathBuf) -> Result<BinaryMerkle> {
    let log = open_oplog(oplog_path)?;
    let ops: Vec<Operation> = log.all_ops_in_hlc_order().map_err(Error::Append)?;
    let mut commit = BinaryMerkle::new();
    for op in &ops {
        commit.apply(op);
    }
    Ok(commit)
}

fn compute_state_root(oplog_path: &PathBuf) -> Result<StateRoot> {
    let commit = rebuild_commit_layer(oplog_path)?;
    let root = commit.root();
    drop(commit);
    Ok(root)
}

fn open_oplog(path: &PathBuf) -> Result<OpLog> {
    OpLog::open(path).map_err(|e| Error::OpenOplog {
        path: path.display().to_string(),
        source: e,
    })
}
