//! `InterceptorServiceImpl` — the Postgres-backed §9.4 interceptor resolver
//! injected into `Services`. It maps `(cx.app_id, service, op)` to the nearest
//! interceptor marker on the calling app's chain and materializes its script
//! **sealed to the declaring owner** (via [`crate::interceptor_repo`]). Running
//! that script is the executor's job (the `invoke()` re-entry core), which keeps
//! `executor-core` Postgres-free.

use async_trait::async_trait;
use picloud_shared::{
    InterceptorResolution, InterceptorService, ResolvedScript, ScriptId, SdkCallCx,
};
use sqlx::PgPool;

pub struct InterceptorServiceImpl {
    pool: PgPool,
}

impl InterceptorServiceImpl {
    #[must_use]
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }
}

#[async_trait]
impl InterceptorService for InterceptorServiceImpl {
    async fn resolve_before(
        &self,
        cx: &SdkCallCx,
        service: &str,
        op: &str,
    ) -> Result<InterceptorResolution, String> {
        // `app_id` derives from `cx` (never a script arg) — the isolation
        // boundary; the chain walk then scopes resolution to this app's subtree.
        let sealed = crate::interceptor_repo::resolve_before(&self.pool, cx.app_id, service, op)
            .await
            .map_err(|e| e.to_string())?;
        let Some(sealed) = sealed else {
            return Ok(InterceptorResolution::None);
        };
        // A marker exists but its sealed script is missing/disabled → dangling,
        // the executor fails closed (denies) rather than silently allowing.
        let Some((sid, source, updated_at)) = sealed.script.clone() else {
            return Ok(InterceptorResolution::Dangling(sealed.script_name));
        };
        // The execution boundary is always the CALLER's app; the `owner` is the
        // sealing (declaring) owner, the lexical origin for the interceptor's
        // own `import`s (§5.5) — a group marker's script runs its imports from
        // the group.
        Ok(InterceptorResolution::Run(Box::new(ResolvedScript {
            script_id: ScriptId::from(sid),
            app_id: cx.app_id,
            owner: Some(sealed.sealing_owner()),
            source,
            updated_at,
            name: sealed.script_name,
        })))
    }
}
