//! `InboxResolver` — abstraction the dispatcher uses to deliver sync
//! HTTP results back to the orchestrator that's awaiting them on a
//! oneshot channel. Lives in `picloud-shared` because the dispatcher
//! (manager-core) and the registry impl (orchestrator-core) live in
//! different crates and need a shared trait surface.
//!
//! v1.1.1 ships an in-process implementation in `orchestrator-core`
//! that keeps a `HashMap<inbox_id, oneshot::Sender<...>>`. Cluster
//! mode (v1.3+) swaps this for a Postgres `LISTEN/NOTIFY`-based
//! resolver without touching the dispatcher code (design notes §3
//! implementation table).
//!
//! Until commit 6 wires up the real registry, `NoopInboxResolver`
//! (`Abandoned` for every attempt) keeps the dispatcher able to run.

use async_trait::async_trait;
use uuid::Uuid;

use crate::ExecResponseSummary;

/// Result of trying to hand back a sync-HTTP outcome.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InboxDeliveryOutcome {
    /// Receiver still attached; result was delivered. Dispatcher
    /// deletes the outbox row.
    Delivered,
    /// Receiver was dropped (orchestrator timed out). Dispatcher
    /// writes an `abandoned_executions` row.
    Abandoned,
}

/// Outcome shape the dispatcher delivers to the inbox. Carries enough
/// to reconstruct an HTTP response — full body via JSON, optional
/// error string when the executor reported a failure.
#[derive(Debug, Clone)]
pub enum InboxResult {
    /// Successful execution. `response` is the `ExecResponse` summary
    /// (status code + body + headers + logs).
    Success(ExecResponseSummary),
    /// Failure modes — script threw, op-budget, timeout, etc. The
    /// orchestrator maps these to the design-notes §3 status codes
    /// (422/502/503/504/507/500) when responding to the HTTP caller.
    Failure {
        kind: InboxFailureKind,
        message: String,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InboxFailureKind {
    /// Script's Rhai code threw or hit a runtime error → 502.
    Runtime,
    /// Wall-clock exceeded → 504.
    Timeout,
    /// Operation budget exceeded → 507.
    OperationBudget,
    /// Gate refused admission → 503.
    Overloaded,
    /// Script parse failure / bad-request → 422.
    Validation,
    /// Platform problem (executor crashed, dispatcher crashed, etc.) → 500.
    Platform,
}

#[async_trait]
pub trait InboxResolver: Send + Sync {
    /// Attempt to deliver `result` to the receiver registered under
    /// `inbox_id`. Returns `Delivered` if the channel was alive,
    /// `Abandoned` if the receiver was already dropped (the
    /// orchestrator's timeout fired before the dispatcher got here).
    async fn deliver(&self, inbox_id: Uuid, result: InboxResult) -> InboxDeliveryOutcome;
}

/// Bootstrap impl used before the real registry is wired in. Every
/// delivery is treated as abandoned — the dispatcher records an
/// abandoned-execution row and moves on. Replaced in `build_app` with
/// the in-process `InboxRegistry` from orchestrator-core.
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopInboxResolver;

#[async_trait]
impl InboxResolver for NoopInboxResolver {
    async fn deliver(&self, _inbox_id: Uuid, _result: InboxResult) -> InboxDeliveryOutcome {
        InboxDeliveryOutcome::Abandoned
    }
}
