//! Hot-reload watcher for the pipeline config directory.
//!
//! Enabled by the `pipeline-watcher` cargo feature.
//!
//! Watches `site/pipelines/` (or the parent directory of the configured NCL
//! path) recursively.  Any `Modify` event triggers a reload of the root NCL
//! file; on success the current subscriber tasks are aborted and a fresh set
//! is spawned from the new config.
//!
//! # Pattern
//!
//! ```text
//! notify::Modify event
//!   → debounce (drain channel)
//!   → load_pipeline_config(path)
//!     Ok  → abort old JoinHandles → spawn_pipeline_subscribers → store new handles
//!     Err → log error, keep existing subscriptions
//! ```

use std::path::PathBuf;
use std::sync::Arc;

use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use tracing::{error, info};

use super::executor::PipelineContext;
use super::pipeline::load_pipeline_config;
use crate::nats::NatsAdminClient;
use crate::startup::nats::spawn_pipeline_subscribers;

/// Watch the pipeline config directory for modifications and hot-reload.
///
/// `path` is the root NCL file (e.g. `site/pipelines/index.ncl`).  The
/// parent directory is watched recursively so edits to `nats.ncl`,
/// `ci_cd.ncl`, or any imported contract file trigger a reload.
///
/// This future runs indefinitely.  The caller should `tokio::spawn` it.
pub async fn watch_pipeline_config(
    path: PathBuf,
    client: NatsAdminClient,
    ctx: Arc<PipelineContext>,
) -> notify::Result<()> {
    let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(8);

    let mut watcher = RecommendedWatcher::new(
        move |res: notify::Result<Event>| {
            if let Ok(ev) = res {
                if matches!(ev.kind, EventKind::Modify(_)) {
                    let _ = tx.blocking_send(());
                }
            }
        },
        Config::default(),
    )?;

    // Watch the containing directory recursively — index.ncl imports nats.ncl
    // and ci_cd.ncl; any of them changing should trigger a reload.
    let watch_dir = path.parent().unwrap_or(path.as_path());
    watcher.watch(watch_dir, RecursiveMode::Recursive)?;

    info!(
        path  = %path.display(),
        watch = %watch_dir.display(),
        "pipeline watcher started"
    );

    // Initial load — spawn subscribers before entering the event loop.
    let mut handles = {
        match load_pipeline_config(&path) {
            Ok(cfg) => spawn_pipeline_subscribers(client.clone(), cfg.pipelines, Arc::clone(&ctx)),
            Err(e) => {
                error!(path = %path.display(), error = %e, "pipeline initial load failed");
                Vec::new()
            }
        }
    };

    while rx.recv().await.is_some() {
        // Debounce: drain any rapid follow-up events before processing.
        while rx.try_recv().is_ok() {}

        match load_pipeline_config(&path) {
            Ok(cfg) => {
                let count = cfg.pipelines.len();
                // Abort all current subscriber tasks; they exit at their next
                // .await point (next NATS message or subscription poll).
                for h in handles.drain(..) {
                    h.abort();
                }
                info!(
                    path      = %path.display(),
                    pipelines = count,
                    "pipelines reloaded — subscriptions restarted"
                );
                handles =
                    spawn_pipeline_subscribers(client.clone(), cfg.pipelines, Arc::clone(&ctx));
            }
            Err(e) => {
                error!(
                    path  = %path.display(),
                    error = %e,
                    "pipeline reload failed — keeping existing subscriptions"
                );
            }
        }
    }

    Ok(())
}
