//! The **ClaimStore seam** (H0a) — the interface that makes "kernel second" a
//! drop-in upgrade, not a rewire.
//!
//! Both Eidos and Veris code against this trait. The file-backed, fold-on-read
//! impl ([`crate::FileClaimStore`]) ships now and kills the F2 write race; the
//! kernel-backed impl swaps in later at the constructor, not at every call site.

use serde::{Deserialize, Serialize};

use crate::{Claim, ClaimKind, ClaimStatus};

/// The event kinds carried on `<project>/.aether/ledger.jsonl` lines. Mirrors the
/// `event` enum in `schema/ledger-event.schema.json`. Payload events
/// (`claim_proposed`/`proven`/`contradicted`) carry a full [`Claim`]; status-only
/// events (`claim_invalidated`, …) carry just the transition.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventKind {
    ClaimProposed,
    ClaimProven,
    ClaimContradicted,
    ClaimInvalidated,
    ClaimRevoked,
    ReverifyQueued,
    ClaimUpdated,
}

/// One append-only line in `.aether/ledger.jsonl` — the sole source of truth.
/// `claims/<id>.json` is its derived projection (see [`crate::FileClaimStore::materialize`]).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LedgerEvent {
    pub ts: String,
    pub event: EventKind,
    pub claim_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub from: Option<String>,
    /// The target status label (kept as the raw wire string so unfamiliar
    /// `reverify_queued`-style targets never break a parse; the fold validates it).
    pub to: String,
    pub actor: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub evidence: Option<String>,
    /// Full claim payload — present on payload events so the ledger alone can
    /// rebuild `claims/`; absent on status-only events (e.g. `claim_invalidated`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub claim: Option<Claim>,
}

/// Optional predicate for [`ClaimStore::list`].
#[derive(Debug, Clone, Default)]
pub struct ClaimFilter {
    pub status: Option<ClaimStatus>,
    pub kind: Option<ClaimKind>,
    pub subject_prefix: Option<String>,
}

/// Errors a [`ClaimStore`] can return.
#[derive(Debug)]
pub enum StoreError {
    NotFound(String),
    InvalidTransition { from: String, to: String },
    SchemaViolation(String),
    Io(std::io::Error),
}

impl std::fmt::Display for StoreError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            StoreError::NotFound(id) => write!(f, "claim not found: {id}"),
            StoreError::InvalidTransition { from, to } => {
                write!(f, "illegal transition: {from} -> {to}")
            }
            StoreError::SchemaViolation(m) => write!(f, "schema violation: {m}"),
            StoreError::Io(e) => write!(f, "io error: {e}"),
        }
    }
}

impl std::error::Error for StoreError {}

impl From<std::io::Error> for StoreError {
    fn from(e: std::io::Error) -> Self {
        StoreError::Io(e)
    }
}

/// The H0a boundary. File-backed now (Phase 0), kernel-backed later (Phase 2);
/// callers (Eidos `invalidate`, Veris `transition`, the DAG-brain readers) code
/// against this and get the kernel as a constructor swap.
pub trait ClaimStore: Send + Sync {
    /// Read a single claim by id, folded fresh from the ledger.
    fn read(&self, id: &str) -> Result<Claim, StoreError>;

    /// List claims (folded fresh), optionally filtered.
    fn list(&self, filter: Option<ClaimFilter>) -> Result<Vec<Claim>, StoreError>;

    /// The ledger events for one claim, in append order.
    fn history(&self, id: &str) -> Result<Vec<LedgerEvent>, StoreError>;

    /// Validate a status change is legal, then append the transition event
    /// (append-only — the claim file is NOT written here; readers fold it).
    fn transition(
        &self,
        id: &str,
        to: ClaimStatus,
        actor: &str,
        evidence: Option<&str>,
    ) -> Result<Claim, StoreError>;

    /// Append a raw event to the ledger (for non-transition events).
    fn append_event(&self, event: LedgerEvent) -> Result<(), StoreError>;
}
