//! Core wire vocabularies, validation helpers, frame context, and payload shapes.

use serde::{Deserialize, Serialize};

use crate::SCHEMA_VERSION;

// ============================================================================
// Wire enums
// ============================================================================

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IntegrationMode {
    ManualSkill,
    LauncherWrapper,
    NativeHook,
    ReferenceAdapter,
    TelemetryOnly,
}

impl IntegrationMode {
    pub const ALL: &'static [Self] = &[
        Self::ManualSkill,
        Self::LauncherWrapper,
        Self::NativeHook,
        Self::ReferenceAdapter,
        Self::TelemetryOnly,
    ];
}

/// Support states for adapter capability claims.
///
/// Per `docs/specs/lifecycle-contract/body.md` ("Support states"), the
/// pre-issue-#6 vocabulary distinguished `simulated` from `inferred`.
/// Issue #6 simplified to one synthesizing state plus `partial`:
///
/// * `simulated` → renamed to `synthesized` (clearer about derivation).
/// * `inferred` → folded into `partial` (telemetry-derived behavior is
///   partial behavior).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SupportState {
    Native,
    Synthesized,
    Manual,
    Partial,
    Unavailable,
}

impl SupportState {
    pub const ALL: &'static [Self] = &[
        Self::Native,
        Self::Synthesized,
        Self::Manual,
        Self::Partial,
        Self::Unavailable,
    ];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AdapterRole {
    PrimaryWorker,
    Worker,
    Supervisor,
    Observer,
}

impl AdapterRole {
    pub const ALL: &'static [Self] = &[
        Self::PrimaryWorker,
        Self::Worker,
        Self::Supervisor,
        Self::Observer,
    ];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
pub enum LifecycleEventKind {
    #[serde(rename = "session.starting")]
    SessionStarting,
    #[serde(rename = "session.started")]
    SessionStarted,
    #[serde(rename = "frame.opening")]
    FrameOpening,
    #[serde(rename = "frame.opened")]
    FrameOpened,
    #[serde(rename = "context.pressure_observed")]
    ContextPressureObserved,
    #[serde(rename = "context.compacted")]
    ContextCompacted,
    #[serde(rename = "frame.ending")]
    FrameEnding,
    #[serde(rename = "frame.ended")]
    FrameEnded,
    #[serde(rename = "session.ending")]
    SessionEnding,
    #[serde(rename = "session.ended")]
    SessionEnded,
    #[serde(rename = "supervisor.tick")]
    SupervisorTick,
    #[serde(rename = "capability.degraded")]
    CapabilityDegraded,
    #[serde(rename = "receipt.emitted")]
    ReceiptEmitted,
    #[serde(rename = "receipt.gap_detected")]
    ReceiptGapDetected,
}

impl LifecycleEventKind {
    pub const ALL: &'static [Self] = &[
        Self::SessionStarting,
        Self::SessionStarted,
        Self::FrameOpening,
        Self::FrameOpened,
        Self::ContextPressureObserved,
        Self::ContextCompacted,
        Self::FrameEnding,
        Self::FrameEnded,
        Self::SessionEnding,
        Self::SessionEnded,
        Self::SupervisorTick,
        Self::CapabilityDegraded,
        Self::ReceiptEmitted,
        Self::ReceiptGapDetected,
    ];
}

pub fn lifecycle_event_kinds() -> Vec<LifecycleEventKind> {
    LifecycleEventKind::ALL.to_vec()
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReceiptStatus {
    Observed,
    Delivered,
    Skipped,
    Degraded,
    Failed,
}

impl ReceiptStatus {
    pub const ALL: &'static [Self] = &[
        Self::Observed,
        Self::Delivered,
        Self::Skipped,
        Self::Degraded,
        Self::Failed,
    ];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FailureClass {
    AdapterUnavailable,
    CapabilityUnsupported,
    CapabilityDegraded,
    PlacementUnavailable,
    PayloadTooLarge,
    PayloadRejected,
    IdentityUnavailable,
    TransportError,
    Timeout,
    OperatorRequired,
    StateConflict,
    InvalidRequest,
    InternalError,
}

impl FailureClass {
    pub const ALL: &'static [Self] = &[
        Self::AdapterUnavailable,
        Self::CapabilityUnsupported,
        Self::CapabilityDegraded,
        Self::PlacementUnavailable,
        Self::PayloadTooLarge,
        Self::PayloadRejected,
        Self::IdentityUnavailable,
        Self::TransportError,
        Self::Timeout,
        Self::OperatorRequired,
        Self::StateConflict,
        Self::InvalidRequest,
        Self::InternalError,
    ];

    /// Default retry-class mapping per the spec's failure-to-retry table.
    pub fn default_retry(self) -> RetryClass {
        match self {
            Self::AdapterUnavailable => RetryClass::RetryAfterReconfigure,
            Self::CapabilityUnsupported => RetryClass::DoNotRetry,
            Self::CapabilityDegraded => RetryClass::RetryAfterReread,
            Self::PlacementUnavailable => RetryClass::RetryAfterReconfigure,
            Self::PayloadTooLarge => RetryClass::DoNotRetry,
            Self::PayloadRejected => RetryClass::RetryAfterReconfigure,
            Self::IdentityUnavailable => RetryClass::RetryAfterReconfigure,
            Self::TransportError => RetryClass::SafeRetry,
            Self::Timeout => RetryClass::SafeRetry,
            Self::OperatorRequired => RetryClass::RetryAfterOperator,
            Self::StateConflict => RetryClass::RetryAfterReread,
            Self::InvalidRequest => RetryClass::DoNotRetry,
            Self::InternalError => RetryClass::RetryAfterReread,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RetryClass {
    SafeRetry,
    RetryAfterReread,
    RetryAfterReconfigure,
    RetryAfterOperator,
    DoNotRetry,
}

impl RetryClass {
    pub const ALL: &'static [Self] = &[
        Self::SafeRetry,
        Self::RetryAfterReread,
        Self::RetryAfterReconfigure,
        Self::RetryAfterOperator,
        Self::DoNotRetry,
    ];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PlacementClass {
    DeveloperEquivalentFrame,
    PrePromptFrame,
    SideChannelContext,
    ReceiptOnly,
}

impl PlacementClass {
    pub const ALL: &'static [Self] = &[
        Self::DeveloperEquivalentFrame,
        Self::PrePromptFrame,
        Self::SideChannelContext,
        Self::ReceiptOnly,
    ];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PlacementOutcome {
    Delivered,
    Skipped,
    Degraded,
    Failed,
}

impl PlacementOutcome {
    pub const ALL: &'static [Self] =
        &[Self::Delivered, Self::Skipped, Self::Degraded, Self::Failed];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RequirementLevel {
    Required,
    Preferred,
    Optional,
}

impl RequirementLevel {
    pub const ALL: &'static [Self] = &[Self::Required, Self::Preferred, Self::Optional];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NegotiationOutcome {
    Satisfied,
    Degraded,
    Unsupported,
    RequiresOperator,
}

impl NegotiationOutcome {
    pub const ALL: &'static [Self] = &[
        Self::Satisfied,
        Self::Degraded,
        Self::Unsupported,
        Self::RequiresOperator,
    ];
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FrameClass {
    TopLevel,
    Subcall,
}

impl FrameClass {
    pub const ALL: &'static [Self] = &[Self::TopLevel, Self::Subcall];
}

// ============================================================================
// Validation
// ============================================================================

/// Reasons a Lifeloop envelope, payload, or receipt failed validation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind", content = "detail")]
pub enum ValidationError {
    EmptyField(String),
    SchemaVersionMismatch { expected: String, found: String },
    InvalidFrameContext(String),
    InvalidPayload(String),
    InvalidReceipt(String),
    InvalidRequest(String),
    InvalidResponse(String),
    InvalidManifest(String),
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::EmptyField(name) => write!(f, "empty sentinel string in field `{name}`"),
            Self::SchemaVersionMismatch { expected, found } => write!(
                f,
                "schema_version mismatch: expected `{expected}`, found `{found}`"
            ),
            Self::InvalidFrameContext(msg) => write!(f, "invalid frame_context: {msg}"),
            Self::InvalidPayload(msg) => write!(f, "invalid payload: {msg}"),
            Self::InvalidReceipt(msg) => write!(f, "invalid receipt: {msg}"),
            Self::InvalidRequest(msg) => write!(f, "invalid callback request: {msg}"),
            Self::InvalidResponse(msg) => write!(f, "invalid callback response: {msg}"),
            Self::InvalidManifest(msg) => write!(f, "invalid adapter manifest: {msg}"),
        }
    }
}

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

pub(crate) fn require_non_empty(value: &str, field: &'static str) -> Result<(), ValidationError> {
    if value.is_empty() {
        return Err(ValidationError::EmptyField(field.to_string()));
    }
    Ok(())
}

// ============================================================================
// Frame context
// ============================================================================

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FrameContext {
    pub frame_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parent_frame_id: Option<String>,
    pub frame_class: FrameClass,
}

impl FrameContext {
    pub fn top_level(frame_id: impl Into<String>) -> Self {
        Self {
            frame_id: frame_id.into(),
            parent_frame_id: None,
            frame_class: FrameClass::TopLevel,
        }
    }

    pub fn subcall(frame_id: impl Into<String>, parent_frame_id: impl Into<String>) -> Self {
        Self {
            frame_id: frame_id.into(),
            parent_frame_id: Some(parent_frame_id.into()),
            frame_class: FrameClass::Subcall,
        }
    }

    pub fn validate(&self) -> Result<(), ValidationError> {
        require_non_empty(&self.frame_id, "frame_context.frame_id")?;
        if let Some(parent) = &self.parent_frame_id {
            require_non_empty(parent, "frame_context.parent_frame_id")?;
        }
        match (self.frame_class, &self.parent_frame_id) {
            (FrameClass::TopLevel, Some(_)) => Err(ValidationError::InvalidFrameContext(
                "frame_class=top_level must not carry parent_frame_id".into(),
            )),
            (FrameClass::Subcall, None) => Err(ValidationError::InvalidFrameContext(
                "frame_class=subcall requires parent_frame_id".into(),
            )),
            _ => Ok(()),
        }
    }
}

// ============================================================================
// Payload envelope and references
// ============================================================================

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AcceptablePlacement {
    pub placement: PlacementClass,
    pub requirement: RequirementLevel,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadRef {
    pub payload_id: String,
    pub payload_kind: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_digest: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub byte_size: Option<u64>,
}

impl PayloadRef {
    pub fn validate(&self) -> Result<(), ValidationError> {
        require_non_empty(&self.payload_id, "payload_ref.payload_id")?;
        require_non_empty(&self.payload_kind, "payload_ref.payload_kind")?;
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadEnvelope {
    pub schema_version: String,
    pub payload_id: String,
    pub client_id: String,
    pub payload_kind: String,
    pub format: String,
    pub content_encoding: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body_ref: Option<String>,
    pub byte_size: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_digest: Option<String>,
    pub acceptable_placements: Vec<AcceptablePlacement>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub idempotency_key: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expires_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub redaction: Option<String>,
    #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
    pub metadata: serde_json::Map<String, serde_json::Value>,
}

impl PayloadEnvelope {
    pub fn effective_byte_size(&self) -> u64 {
        self.body
            .as_ref()
            .map(|body| body.len() as u64)
            .unwrap_or(self.byte_size)
    }

    pub fn validate(&self) -> Result<(), ValidationError> {
        if self.schema_version != SCHEMA_VERSION {
            return Err(ValidationError::SchemaVersionMismatch {
                expected: SCHEMA_VERSION.to_string(),
                found: self.schema_version.clone(),
            });
        }
        require_non_empty(&self.payload_id, "payload.payload_id")?;
        require_non_empty(&self.client_id, "payload.client_id")?;
        require_non_empty(&self.payload_kind, "payload.payload_kind")?;
        require_non_empty(&self.format, "payload.format")?;
        require_non_empty(&self.content_encoding, "payload.content_encoding")?;
        match (self.body.is_some(), self.body_ref.is_some()) {
            (true, true) => Err(ValidationError::InvalidPayload(
                "body and body_ref are mutually exclusive".into(),
            )),
            (false, false) => Err(ValidationError::InvalidPayload(
                "exactly one of body or body_ref must be present".into(),
            )),
            _ => Ok(()),
        }?;
        if let Some(body) = &self.body {
            let actual = body.len() as u64;
            if actual != self.byte_size {
                return Err(ValidationError::InvalidPayload(format!(
                    "body byte length {actual} does not match byte_size {}",
                    self.byte_size
                )));
            }
        }
        if let Some(idem) = &self.idempotency_key {
            require_non_empty(idem, "payload.idempotency_key")?;
        }
        if self.acceptable_placements.is_empty() {
            return Err(ValidationError::InvalidPayload(
                "acceptable_placements must list at least one placement".into(),
            ));
        }
        Ok(())
    }
}

// ============================================================================
// Receipts
// ============================================================================

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PayloadReceipt {
    pub payload_id: String,
    pub payload_kind: String,
    pub placement: PlacementClass,
    pub status: PlacementOutcome,
    pub byte_size: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_digest: Option<String>,
}

impl PayloadReceipt {
    pub fn validate(&self) -> Result<(), ValidationError> {
        require_non_empty(&self.payload_id, "payload_receipt.payload_id")?;
        require_non_empty(&self.payload_kind, "payload_receipt.payload_kind")?;
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CapabilityDegradation {
    pub capability: String,
    pub previous_support: SupportState,
    pub current_support: SupportState,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub evidence: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub retry_class: Option<RetryClass>,
}

impl CapabilityDegradation {
    pub fn validate(&self) -> Result<(), ValidationError> {
        require_non_empty(&self.capability, "capability_degradation.capability")?;
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Warning {
    pub code: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub capability: Option<String>,
}

impl Warning {
    pub fn validate(&self) -> Result<(), ValidationError> {
        require_non_empty(&self.code, "warning.code")?;
        Ok(())
    }
}
