//! The external-evidence provider port.
//!
//! A sibling product (tramiton, for firmware) may already hold authoritative
//! analysis for an artifact. An [`EvidenceProvider`] lets compliance-scanner
//! *reconcile* that evidence — a build plan, an SBOM, a VEX document, a
//! reproducible-build lock, an attestation — instead of recomputing it. The key
//! used to match is the artifact content digest (a firmware sha256, which equals
//! [`crate::models::Artifact::content_hash`]).
//!
//! Concrete providers live in the agent (a tramiton CLI shell-out today, a cloud
//! client later) plus a deterministic mock for tests, so nothing here depends on
//! an external binary.

use std::path::Path;

use crate::error::CoreError;
use crate::models::{Artifact, ExternalSystem};

/// A single reconcilable evidence document fetched from a sibling product.
#[derive(Debug, Clone)]
pub struct EvidenceDocument {
    /// What the document is: `build_plan` | `sbom` | `vex` | `lock` | `attestation`.
    pub kind: String,
    /// The document's format (e.g. `cyclonedx-1.5`, `openvex-0.2.0`, `toml`, `json`).
    pub format: String,
    /// The raw document payload.
    pub content: String,
}

/// The evidence a provider could return for a target's artifact.
#[derive(Debug, Clone, Default)]
pub struct ReconciledEvidence {
    /// The sibling's project identifier, if resolved.
    pub project_id: Option<String>,
    /// The subject content digest the evidence pertains to.
    pub subject_sha256: Option<String>,
    /// The documents fetched (any of build plan / SBOM / VEX / lock / attestation).
    pub documents: Vec<EvidenceDocument>,
}

/// A source of externally-held, reconcilable evidence for an artifact.
#[allow(async_fn_in_trait)]
pub trait EvidenceProvider: Send + Sync {
    /// Which sibling product this provider integrates.
    fn system(&self) -> ExternalSystem;

    /// Whether this provider can handle the given artifact + working path
    /// (e.g. tramiton handles firmware images / embedded source trees).
    fn handles(&self, artifact: &Artifact, working_path: Option<&Path>) -> bool;

    /// Reconcile existing evidence for the artifact, keyed by its content digest.
    /// Returns `Ok(None)` when the provider has nothing for this artifact.
    async fn reconcile(
        &self,
        artifact: &Artifact,
        working_path: Option<&Path>,
    ) -> Result<Option<ReconciledEvidence>, CoreError>;
}
