//! Abstraction for parse-time script validation.
//!
//! Lives in `shared` so the manager can call into validation logic
//! without taking a hard dep on the executor crate. The executor-core
//! crate provides the impl by registering `Engine` as a `ScriptValidator`.

use thiserror::Error;

#[derive(Debug, Error)]
pub enum ValidationError {
    #[error("invalid script source: {0}")]
    Syntax(String),

    /// v1.1.3: source compiled but failed the module-shape gate
    /// (top-level statements other than `fn` / `const` / `import`).
    #[error("module syntax error: {0}")]
    ModuleShape(String),
}

/// Output of a successful validate. v1.1.3 carries the list of literal
/// `import "<name>"` paths the script declares — the manager writes
/// these into the `script_imports` dep-graph table. Endpoints may also
/// have imports; the field is populated unconditionally.
#[derive(Debug, Clone, Default)]
pub struct ValidatedScript {
    /// Literal-path imports (in declaration order). Dynamic imports
    /// `import some_var as y;` are not captured — the resolver still
    /// honors them at runtime, but the dep graph only tracks names
    /// known at compile time.
    pub imports: Vec<String>,
}

pub trait ScriptValidator: Send + Sync {
    /// Endpoint-shape validation: parse-only syntax check. Returns the
    /// declared (literal) imports so the manager can populate the
    /// dep-graph table on save.
    fn validate(&self, source: &str) -> Result<ValidatedScript, ValidationError>;

    /// Module-shape validation: parse + reject any top-level
    /// statement that isn't `fn` / `const` / `import`. Default impl
    /// rejects every module so non-engine validators stay simple
    /// (tests / stubs don't need to know module rules).
    fn validate_module(&self, _source: &str) -> Result<ValidatedScript, ValidationError> {
        Err(ValidationError::ModuleShape(
            "module validation not implemented by this validator".into(),
        ))
    }
}
