pub mod planner;
pub mod runtime;

use std::ffi::OsString;
use std::path::PathBuf;

use crate::config::DeepSeekSettings;
use crate::pipe::{AgentMessage, BrowserMessage, BrowserPipeTool, PipeError, Transport};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentRuntimeContext {
    config_path: Option<PathBuf>,
    workspace_root: PathBuf,
}

impl AgentRuntimeContext {
    pub fn new(config_path: Option<PathBuf>, workspace_root: PathBuf) -> Self {
        Self {
            config_path,
            workspace_root,
        }
    }

    pub fn from_process_args<I, S>(args: I) -> Result<Self, PipeError>
    where
        I: IntoIterator<Item = S>,
        S: Into<OsString>,
    {
        let mut config_path = None;
        let mut args = args.into_iter().map(Into::into);
        let _ = args.next();

        while let Some(arg) = args.next() {
            if arg == OsString::from("--config-path") {
                let Some(value) = args.next() else {
                    return Err(PipeError::Protocol(
                        "missing value for --config-path".to_string(),
                    ));
                };
                config_path = Some(PathBuf::from(value));
                continue;
            }

            let arg_string = arg.to_string_lossy();
            if let Some(value) = arg_string.strip_prefix("--config-path=") {
                config_path = Some(PathBuf::from(value));
            }
        }

        let workspace_root = config_path
            .as_ref()
            .and_then(|path| path.parent().map(|parent| parent.to_path_buf()))
            .unwrap_or_else(default_workspace_root);

        Ok(Self::new(config_path, workspace_root))
    }

    fn load_deepseek_settings(&self) -> Result<Option<DeepSeekSettings>, PipeError> {
        DeepSeekSettings::load(self.config_path.as_deref())
            .map_err(|err| PipeError::Protocol(err.to_string()))
    }

    fn deepseek_source_label(&self) -> String {
        match &self.config_path {
            Some(path) if path.exists() => path.display().to_string(),
            _ => "environment".to_string(),
        }
    }
}

impl Default for AgentRuntimeContext {
    fn default() -> Self {
        Self::new(None, default_workspace_root())
    }
}

fn default_workspace_root() -> PathBuf {
    std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
}

fn send_mode_log<T: Transport>(transport: &T, mode: &str) -> Result<(), PipeError> {
    transport.send(&AgentMessage::LogEntry {
        level: "mode".to_string(),
        message: mode.to_string(),
    })
}

fn execute_plan<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    plan: &planner::TaskPlan,
) -> Result<String, PipeError> {
    for step in &plan.steps {
        transport.send(&AgentMessage::LogEntry {
            level: "info".to_string(),
            message: step.log_message.clone(),
        })?;

        let result = browser_tool.invoke(
            step.action.clone(),
            step.params.clone(),
            &step.expected_domain,
        )?;
        if !result.success {
            return Err(PipeError::Protocol(format!(
                "browser action failed: {}",
                result.data
            )));
        }
    }

    Ok(plan.summary.clone())
}

pub fn execute_task<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    instruction: &str,
) -> Result<String, PipeError> {
    let plan = planner::plan_instruction(instruction)
        .map_err(|err| PipeError::Protocol(err.to_string()))?;
    execute_plan(transport, browser_tool, &plan)
}

pub fn handle_browser_message<T: Transport + 'static>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    message: BrowserMessage,
) -> Result<(), PipeError> {
    handle_browser_message_with_context(
        transport,
        browser_tool,
        &AgentRuntimeContext::default(),
        message,
    )
}

pub fn handle_browser_message_with_context<T: Transport + 'static>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    context: &AgentRuntimeContext,
    message: BrowserMessage,
) -> Result<(), PipeError> {
    match message {
        BrowserMessage::SubmitTask { instruction } => {
            let completion = match context.load_deepseek_settings() {
                Ok(Some(settings)) => {
                    let _ = transport.send(&AgentMessage::LogEntry {
                        level: "info".to_string(),
                        message: format!(
                            "DeepSeek config loaded from {} model={} base_url={}",
                            context.deepseek_source_label(),
                            settings.model,
                            settings.base_url
                        ),
                    });
                    let _ = send_mode_log(transport, "compat_llm_primary");
                    match crate::compat::runtime::execute_task(
                        transport,
                        browser_tool.clone(),
                        &instruction,
                        &context.workspace_root,
                        &settings,
                    ) {
                        Ok(summary) => AgentMessage::TaskComplete {
                            success: true,
                            summary,
                        },
                        Err(err) => AgentMessage::TaskComplete {
                            success: false,
                            summary: err.to_string(),
                        },
                    }
                }
                Ok(None) => match planner::plan_instruction(&instruction) {
                    Ok(plan) => {
                        let _ = send_mode_log(transport, "deterministic_planner");
                        match execute_plan(transport, browser_tool, &plan) {
                            Ok(summary) => AgentMessage::TaskComplete {
                                success: true,
                                summary,
                            },
                            Err(err) => AgentMessage::TaskComplete {
                                success: false,
                                summary: err.to_string(),
                            },
                        }
                    }
                    Err(err) => AgentMessage::TaskComplete {
                        success: false,
                        summary: PipeError::Protocol(err.to_string()).to_string(),
                    },
                },
                Err(err) => {
                    let _ = transport.send(&AgentMessage::LogEntry {
                        level: "error".to_string(),
                        message: format!("failed to load DeepSeek config: {err}"),
                    });
                    AgentMessage::TaskComplete {
                        success: false,
                        summary: err.to_string(),
                    }
                }
            };
            transport.send(&completion)
        }
        BrowserMessage::Init { .. } => {
            eprintln!("ignoring duplicate init after handshake");
            Ok(())
        }
        BrowserMessage::Response { seq, .. } => {
            eprintln!("ignoring unsolicited response: seq={seq}");
            Ok(())
        }
    }
}
