#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]

//! Extension subsystem — spawn-per-event subprocess hooks.
//!
//! See `docs/superpowers/specs/2026-05-21-capo-v0.7-design.md` for the
//! design rationale (spawn-per-event vs long-lived; codex-style; minimal
//! scope).
//!
//! Module structure mirrors `mcp/`:
//!   manifest.rs  — TOML schema + parsing
//!   wire.rs      — Event / Action serde types (JSONL line shape)
//!   registry.rs  — validated, indexed view of loaded extensions
//!   dispatcher.rs — spawn-and-await; chain semantics; timeout
//!   diagnostic.rs — surfaced via `/extensions` slash command

pub mod diagnostic;
pub mod dispatcher;
pub mod manifest;
pub mod registry;
pub mod wire;

pub use diagnostic::{DiagnosticSeverity, ExtensionDiagnostic};
pub use manifest::{parse_str, ExtensionEntry, ExtensionManifestFile};
pub use registry::{ExtensionRegistry, RegisteredExtension};
pub use wire::{Action, Event, EventName};

/// Load the manifest at `path` (missing-file = empty registry, no error)
/// and validate it. Returns the registry and any diagnostics collected.
pub async fn load_extensions_manifest(
    path: &std::path::Path,
) -> (ExtensionRegistry, Vec<ExtensionDiagnostic>) {
    use diagnostic::DiagnosticSeverity;

    let raw = match tokio::fs::read_to_string(path).await {
        Ok(s) => s,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
            return (ExtensionRegistry::default(), Vec::new());
        }
        Err(err) => {
            let display = path.display();
            let diag = ExtensionDiagnostic {
                extension_name: "<manifest>".into(),
                severity: DiagnosticSeverity::Error,
                message: format!("could not read {display}: {err}"),
            };
            return (ExtensionRegistry::default(), vec![diag]);
        }
    };
    let parsed = match manifest::parse_str(&raw) {
        Ok(p) => p,
        Err(err) => {
            let display = path.display();
            let diag = ExtensionDiagnostic {
                extension_name: "<manifest>".into(),
                severity: DiagnosticSeverity::Error,
                message: format!("could not parse {display}: {err}"),
            };
            return (ExtensionRegistry::default(), vec![diag]);
        }
    };
    let mut diagnostics = Vec::new();
    let registry = ExtensionRegistry::build(parsed, &mut diagnostics);
    (registry, diagnostics)
}
