/// Bridge between agor-core (PtyManager, SidecarManager) and Dioxus signals.
///
/// In the Tauri app, TauriEventSink implements EventSink by emitting Tauri events
/// that the Svelte frontend listens to. Here we implement EventSink to push
/// events into Dioxus signals, demonstrating native Rust -> UI reactivity
/// without any IPC layer.

use std::sync::{Arc, Mutex};

use agor_core::event::EventSink;
use agor_core::pty::{PtyManager, PtyOptions};
use agor_core::sidecar::{AgentQueryOptions, SidecarConfig, SidecarManager};

/// Collects events emitted by PtyManager and SidecarManager.
/// In a real app, these would drive Dioxus signal updates.
///
/// Key advantage over Tauri: no serialization/deserialization overhead.
/// Events are typed Rust values, not JSON blobs crossing an IPC bridge.
#[derive(Debug, Clone)]
pub struct DioxusEventSink {
    /// Buffered events — a real implementation would use channels or
    /// direct signal mutation via Dioxus's `schedule_update`.
    events: Arc<Mutex<Vec<AppEvent>>>,
}

/// Typed event enum — replaces the untyped (event_name, JSON) pattern
/// used by the Tauri EventSink.
#[derive(Debug, Clone)]
pub enum AppEvent {
    PtyOutput { id: String, data: String },
    PtyExit { id: String, code: Option<i32> },
    AgentMessage { session_id: String, payload: serde_json::Value },
    AgentReady { provider: String },
    AgentError { session_id: String, error: String },
}

impl DioxusEventSink {
    pub fn new() -> Self {
        Self {
            events: Arc::new(Mutex::new(Vec::new())),
        }
    }

    /// Drain all buffered events. Called from a Dioxus use_effect or timer.
    pub fn drain_events(&self) -> Vec<AppEvent> {
        let mut events = self.events.lock().unwrap();
        std::mem::take(&mut *events)
    }
}

impl EventSink for DioxusEventSink {
    fn emit(&self, event: &str, payload: serde_json::Value) {
        let app_event = match event {
            "pty-output" => {
                let id = payload["id"].as_str().unwrap_or("").to_string();
                let data = payload["data"].as_str().unwrap_or("").to_string();
                AppEvent::PtyOutput { id, data }
            }
            "pty-exit" => {
                let id = payload["id"].as_str().unwrap_or("").to_string();
                let code = payload["code"].as_i64().map(|c| c as i32);
                AppEvent::PtyExit { id, code }
            }
            "agent-message" => {
                let session_id = payload["sessionId"].as_str().unwrap_or("").to_string();
                AppEvent::AgentMessage {
                    session_id,
                    payload: payload.clone(),
                }
            }
            "sidecar-ready" => {
                let provider = payload["provider"].as_str().unwrap_or("claude").to_string();
                AppEvent::AgentReady { provider }
            }
            "agent-error" => {
                let session_id = payload["sessionId"].as_str().unwrap_or("").to_string();
                let error = payload["error"].as_str().unwrap_or("unknown error").to_string();
                AppEvent::AgentError { session_id, error }
            }
            _ => {
                // Unknown event — log and ignore
                log::debug!("Unknown event from backend: {event}");
                return;
            }
        };

        if let Ok(mut events) = self.events.lock() {
            events.push(app_event);
        }
    }
}

/// Backend handle holding initialized managers.
///
/// In the Tauri app, these live in AppState behind Arc<Mutex<>>
/// and are accessed via Tauri commands. Here they're directly
/// available to Dioxus components via use_context.
pub struct Backend {
    pub pty_manager: PtyManager,
    pub sidecar_manager: SidecarManager,
    pub event_sink: DioxusEventSink,
}

impl Backend {
    /// Initialize backend managers.
    ///
    /// # Panics
    /// Panics if sidecar search paths cannot be resolved (mirrors Tauri app behavior).
    pub fn new() -> Self {
        let event_sink = DioxusEventSink::new();
        let sink: Arc<dyn EventSink> = Arc::new(event_sink.clone());

        let pty_manager = PtyManager::new(Arc::clone(&sink));

        // Resolve sidecar search paths — same logic as src-tauri/src/lib.rs
        let mut search_paths = Vec::new();
        if let Ok(exe_dir) = std::env::current_exe() {
            if let Some(parent) = exe_dir.parent() {
                search_paths.push(parent.join("sidecar"));
                search_paths.push(parent.join("../sidecar/dist"));
            }
        }
        // Also check the repo's sidecar/dist for dev mode
        search_paths.push(std::path::PathBuf::from("../sidecar/dist"));
        search_paths.push(std::path::PathBuf::from("./sidecar/dist"));

        let sidecar_config = SidecarConfig {
            search_paths,
            env_overrides: std::collections::HashMap::new(),
            sandbox: agor_core::sandbox::SandboxConfig::default(),
        };

        let sidecar_manager = SidecarManager::new(Arc::clone(&sink), sidecar_config);

        Self {
            pty_manager,
            sidecar_manager,
            event_sink,
        }
    }

    /// Spawn a PTY with default options.
    pub fn spawn_pty(&self, cwd: Option<&str>) -> Result<String, String> {
        self.pty_manager.spawn(PtyOptions {
            shell: None,
            cwd: cwd.map(|s| s.to_string()),
            args: None,
            cols: Some(80),
            rows: Some(24),
        })
    }

    /// Start an agent query.
    pub fn query_agent(&self, session_id: &str, prompt: &str, cwd: &str) -> Result<(), String> {
        let options = AgentQueryOptions {
            provider: "claude".to_string(),
            session_id: session_id.to_string(),
            prompt: prompt.to_string(),
            cwd: Some(cwd.to_string()),
            max_turns: None,
            max_budget_usd: None,
            resume_session_id: None,
            permission_mode: Some("bypassPermissions".to_string()),
            setting_sources: Some(vec!["user".to_string(), "project".to_string()]),
            system_prompt: None,
            model: None,
            claude_config_dir: None,
            additional_directories: None,
            worktree_name: None,
            provider_config: serde_json::Value::Null,
            extra_env: std::collections::HashMap::new(),
        };
        self.sidecar_manager.query(&options)
    }

    /// Stop an agent session.
    pub fn stop_agent(&self, session_id: &str) -> Result<(), String> {
        self.sidecar_manager.stop_session(session_id)
    }
}
