//! Extractors — axum-style typed pulls from the step context.
//!
//! Each extractor implements [`Extract`] for a given saga type. Step handlers
//! list the extractors they need as ordinary function parameters; the
//! framework satisfies them from the [`crate::step::StepContext`].

use std::any::TypeId;
use std::marker::PhantomData;

use serde::de::DeserializeOwned;
use thiserror::Error;

use crate::result::StepError;
use crate::saga::Saga;
use crate::step::{IdempotencyKey, StepContext};

/// Why an extractor couldn't satisfy itself.
#[derive(Debug, Error)]
pub enum ExtractError {
    #[error("no trigger event present in this context (extractor: {0})")]
    NoTrigger(&'static str),
    #[error("no request payload present (Req extractor only valid in `.effect(...)` handlers)")]
    NoRequest(&'static str),
    #[error("no completed upstream output of type `{0}`")]
    MissingOutput(&'static str),
    #[error("no dep of type `{0}` registered on the builder")]
    MissingDep(&'static str),
    #[error("no cause present (extractor only valid in compensation handlers)")]
    NoCause,
    #[error("deserialisation failed for {kind}: {source}")]
    Deserialize {
        kind: &'static str,
        #[source]
        source: serde_json::Error,
    },
    /// Internal invariant violation. Surfaced rather than panicking so a
    /// library bug never crashes the host process.
    #[error("internal invariant violated: {0}")]
    Internal(&'static str),
}

/// Trait every extractor implements. Generic over the saga so the extractor
/// can constrain itself (e.g. `State<S::State>`).
pub trait Extract<S: Saga>: Sized + Send {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError>;

    /// What this extractor needs the topology to provide. Returned at
    /// build time so the validator can check satisfiability before any
    /// traffic arrives. Default: `None` (extractor is always satisfiable,
    /// e.g. `State`).
    fn requirements() -> ExtractRequirement {
        ExtractRequirement::None
    }
}

/// A single extractor's requirement on the topology. Aggregated per
/// step by the `StepHandler` tuple impls and surfaced to the validator.
///
/// Variants carry `type_name` strings (not just `TypeId`) because the
/// validator's panic messages quote them — we want
/// `Output<crate::saga::FlightReserved>` in the diagnostic, not just
/// "an unknown type id."
#[derive(Debug, Clone)]
pub enum ExtractRequirement {
    /// No requirement (always satisfiable).
    None,
    /// The handler reads `Output<T>` — some upstream step must produce `T`.
    UpstreamOutput {
        type_id: TypeId,
        type_name: &'static str,
    },
    /// The handler reads `Dep<T>` — a dep of this type must be
    /// registered via `.dep(value)`.
    Dep {
        type_id: TypeId,
        type_name: &'static str,
    },
    /// The handler reads `Trigger<T>` — the validator checks this `T`
    /// matches the type registered on `start_on::<A, _>(...)`.
    Trigger {
        type_id: TypeId,
        type_name: &'static str,
    },
    /// The handler reads `Cause` — only valid in compensation handlers.
    /// Forward-step usage is flagged at build time.
    CompensationOnly,
    /// The handler reads `Req<T>` — only valid in effect handlers
    /// registered via `.effect(...)`. Forward-step usage is flagged.
    RequestOnly,
}

// =====================================================================
// Trigger<T>: the event payload that started the saga.
// =====================================================================

/// Extracts the trigger event for this saga instance. Only valid in steps
/// reachable from a `start_on` registration. The `T` must be the same payload
/// type the `start_on` correlator produced.
#[derive(Debug, Clone)]
pub struct Trigger<T>(pub T);

impl<S: Saga, T: DeserializeOwned + Send + 'static> Extract<S> for Trigger<T> {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        let value = ctx
            .trigger_json()
            .ok_or(ExtractError::NoTrigger(std::any::type_name::<T>()))?;
        let payload =
            serde_json::from_value::<T>(value.clone()).map_err(|e| ExtractError::Deserialize {
                kind: std::any::type_name::<T>(),
                source: e,
            })?;
        Ok(Trigger(payload))
    }

    fn requirements() -> ExtractRequirement {
        ExtractRequirement::Trigger {
            type_id: TypeId::of::<T>(),
            type_name: std::any::type_name::<T>(),
        }
    }
}

// =====================================================================
// Output<T>: the typed output of a completed upstream step.
// =====================================================================

/// Extracts the typed output of a completed upstream step. The `T` must be the
/// typed output of an upstream step on the DAG (a value handler's return
/// type, or an `.on_response` map's output); the builder
/// validates the topology at registration time.
#[derive(Debug, Clone)]
pub struct Output<T>(pub T);

impl<S: Saga, T: DeserializeOwned + Send + 'static> Extract<S> for Output<T> {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        let type_id = TypeId::of::<T>();
        let value = ctx
            .output_json(type_id)
            .ok_or(ExtractError::MissingOutput(std::any::type_name::<T>()))?;
        let payload =
            serde_json::from_value::<T>(value.clone()).map_err(|e| ExtractError::Deserialize {
                kind: std::any::type_name::<T>(),
                source: e,
            })?;
        Ok(Output(payload))
    }

    fn requirements() -> ExtractRequirement {
        ExtractRequirement::UpstreamOutput {
            type_id: TypeId::of::<T>(),
            type_name: std::any::type_name::<T>(),
        }
    }
}

// =====================================================================
// State<X>: a clone of the saga's state struct.
// =====================================================================

/// Extracts a clone of the saga's state. The framework cloned the state
/// before invoking the handler, so this is cheap and read-only-by-contract.
#[derive(Debug, Clone)]
pub struct State<X>(pub X);

impl<S: Saga> Extract<S> for State<S::State> {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        Ok(State(ctx.state().clone()))
    }
}

// =====================================================================
// Dep<T>: a dependency registered on the builder.
// =====================================================================

/// Extracts a dependency registered via [`crate::SagaBuilder::dep`]. Must be
/// `Clone` because the same dep flows into every handler that requests it.
#[derive(Debug, Clone)]
pub struct Dep<T>(pub T);

impl<S: Saga, T: Clone + Send + Sync + 'static> Extract<S> for Dep<T> {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        let type_id = TypeId::of::<T>();
        let any = ctx
            .dep_any(type_id)
            .ok_or(ExtractError::MissingDep(std::any::type_name::<T>()))?;
        // Should always succeed when TypeId matches — but surface as a
        // typed error rather than panic, so a library bug never crashes
        // the host process.
        let typed = any.downcast_ref::<T>().ok_or(ExtractError::Internal(
            "dep TypeId matched but downcast failed",
        ))?;
        Ok(Dep(typed.clone()))
    }

    fn requirements() -> ExtractRequirement {
        ExtractRequirement::Dep {
            type_id: TypeId::of::<T>(),
            type_name: std::any::type_name::<T>(),
        }
    }
}

// =====================================================================
// Req<R>: the typed request payload for a `.handle::<R>(...)` handler.
// =====================================================================

/// Extracts the typed request payload that a `.handle::<R>(...)` handler is
/// being invoked with. Only valid inside a registered request handler;
/// extraction fails with [`ExtractError::NoRequest`] elsewhere.
#[derive(Debug, Clone)]
pub struct Req<R>(pub R);

impl<S: Saga, R: DeserializeOwned + Send + 'static> Extract<S> for Req<R> {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        let value = ctx
            .request_json()
            .ok_or(ExtractError::NoRequest(std::any::type_name::<R>()))?;
        let payload =
            serde_json::from_value::<R>(value.clone()).map_err(|e| ExtractError::Deserialize {
                kind: std::any::type_name::<R>(),
                source: e,
            })?;
        Ok(Req(payload))
    }

    fn requirements() -> ExtractRequirement {
        ExtractRequirement::RequestOnly
    }
}

// =====================================================================
// IdempotencyKey: the stable key for the effect this handler will run.
// =====================================================================

impl<S: Saga> Extract<S> for IdempotencyKey {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        Ok(ctx.idempotency_key())
    }
    // No topology requirement — always satisfiable (default `None`).
}

// =====================================================================
// Cause: the originating StepError for a compensation handler.
// =====================================================================

/// Extracts the originating step error in a compensation context. Only valid
/// in compensation handlers; in step handlers, returns [`ExtractError::NoCause`].
#[derive(Debug, Clone)]
pub struct Cause(pub StepError);

impl<S: Saga> Extract<S> for Cause {
    fn extract(ctx: &StepContext<'_, S>) -> Result<Self, ExtractError> {
        let cause = ctx.cause().ok_or(ExtractError::NoCause)?;
        Ok(Cause(cause.clone()))
    }

    fn requirements() -> ExtractRequirement {
        ExtractRequirement::CompensationOnly
    }
}

// Phantom-data export to keep the unused-type-parameter lints happy on the
// alias name above.
#[doc(hidden)]
pub struct _Marker<T>(PhantomData<T>);
