pub mod background;
pub mod edit;
pub mod read;
pub mod shell;
pub mod subagent;
pub mod visual;
pub mod web;

use crate::config::{Mode, ToolProfile};
use serde_json::{json, Value};

/// Tool schema advertised to the model. The AHE evolved-harness core stays
/// intact (shell + background tasks); Codex-style additions: update_plan,
/// web_search, read_url, explore (subagent fan-out), analyze_image, plus any
/// connected MCP tools.
pub fn tool_definitions(mcp_tools: &[Value]) -> Value {
    let mut tools = base_tool_definitions();
    if let Some(arr) = tools.as_array_mut() {
        arr.extend(mcp_tools.iter().cloned());
    }
    tools
}

/// The tool list actually advertised for a given profile + mode.
///
/// - `ToolProfile::ReadOnly` (explore subagents): only `read_file` +
///   `run_shell_command` — no edit/write/web tools, no MCP, and no `explore`
///   (children can't spawn more children).
/// - `Mode::Plan`: read_file + explore + update_plan (agentic plan mode: fan
///   out explorers, synthesize a plan, never edit).
/// - `Mode::Research`: web_search + read_url + update_plan (unchanged).
pub fn tools_for(profile: ToolProfile, mode: Mode, mcp_tools: &[Value]) -> Value {
    let mut tools = tool_definitions(mcp_tools);
    if profile == ToolProfile::ReadOnly {
        tools = filter_named(tools, &["read_file", "run_shell_command"]);
    }
    match mode {
        Mode::Agent => {}
        Mode::Plan => tools = filter_named(tools, &["read_file", "explore", "update_plan"]),
        Mode::Research => tools = filter_named(tools, &["web_search", "read_url", "update_plan"]),
    }
    tools
}

/// Keep only the tools whose function name is in `allowed`.
fn filter_named(tools: Value, allowed: &[&str]) -> Value {
    match tools.as_array() {
        Some(arr) => Value::Array(
            arr.iter()
                .filter(|t| {
                    t["function"]["name"]
                        .as_str()
                        .map(|n| allowed.contains(&n))
                        .unwrap_or(false)
                })
                .cloned()
                .collect(),
        ),
        None => tools,
    }
}

/// Sorted function names in a tool-definition array (test/diagnostic helper).
pub fn tool_names(tools: &Value) -> Vec<String> {
    let mut names: Vec<String> = tools
        .as_array()
        .map(|arr| {
            arr.iter()
                .filter_map(|t| t["function"]["name"].as_str().map(String::from))
                .collect()
        })
        .unwrap_or_default();
    names.sort();
    names
}

fn base_tool_definitions() -> Value {
    json!([
        {
            "type": "function",
            "function": {
                "name": "update_plan",
                "description": "Maintain a short step-by-step plan for the current task. Call again to update statuses as you progress. Keep 3-7 steps; exactly one step should be in_progress.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "steps": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "step": { "type": "string" },
                                    "status": { "type": "string", "enum": ["pending", "in_progress", "completed"] }
                                },
                                "required": ["step", "status"]
                            }
                        }
                    },
                    "required": ["steps"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "write_file",
                "description": "Create a new file or overwrite an existing one. Shows the change as a reviewable diff. PREFER this (and edit_file) over shell redirection (>, heredocs, tee) for file changes so edits are visible.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": { "type": "string", "description": "File path (relative to the working directory unless absolute)." },
                        "content": { "type": "string", "description": "Full file content to write." }
                    },
                    "required": ["path", "content"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "edit_file",
                "description": "Replace an exact string in a file with a new string, shown as a reviewable diff. old_string must match exactly and be unique unless replace_all is true. PREFER this over `sed -i` for edits.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": { "type": "string" },
                        "old_string": { "type": "string", "description": "Exact text to replace (include enough surrounding context to be unique)." },
                        "new_string": { "type": "string", "description": "Replacement text." },
                        "replace_all": { "type": "boolean", "description": "Replace every occurrence.", "default": false }
                    },
                    "required": ["path", "old_string", "new_string"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "read_file",
                "description": "Read and parse a file to text. Handles code/text/json/csv/markdown directly, PDF (via pdftotext), docx/pptx (via unzip), and reports image dimensions. For images you want the model to SEE, the user must /attach them with a vision model.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": { "type": "string", "description": "File path to read." }
                    },
                    "required": ["path"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "web_search",
                "description": "Search the web. Use this whenever you are unsure about a fact, an API, a version, an error message, or anything that may have changed — do NOT guess or rely on possibly-stale knowledge.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": { "type": "string", "description": "Search query." },
                        "max_results": { "type": "integer", "default": 5 }
                    },
                    "required": ["query"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "read_url",
                "description": "Fetch a web page (or raw file URL) and return its readable text. Use after web_search to read the actual source.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "url": { "type": "string", "description": "http(s) URL to fetch." }
                    },
                    "required": ["url"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "explore",
                "description": "Fan out read-only explorer subagents over the codebase in parallel; returns their findings. Each task should be a focused question or area to investigate. Explorers can read files and run read-only shell commands only.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "tasks": {
                            "type": "array",
                            "description": "One entry per explorer subagent.",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "prompt": { "type": "string", "description": "What this explorer should investigate and report on." }
                                },
                                "required": ["prompt"]
                            }
                        },
                        "max_agents": { "type": "integer", "description": "Maximum concurrent explorers (default 4, cap 6).", "default": 4 }
                    },
                    "required": ["tasks"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "analyze_image",
                "description": "Analyze a local image file (png/jpg/jpeg/gif/webp, max 8 MB) with a multimodal vision model and return the text answer. Use for screenshots, diagrams, charts and UI mockups.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "path": { "type": "string", "description": "Path to the image file." },
                        "question": { "type": "string", "description": "What to look for or answer about the image (optional; defaults to a detailed description)." }
                    },
                    "required": ["path"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "run_shell_command",
                "description": "Execute a shell command via `bash -c`. Combined stdout/stderr is returned (large output keeps the tail). Set is_background=true for long-running processes (servers, installs, builds) instead of using `&`.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "command": { "type": "string", "description": "The exact command to execute." },
                        "description": { "type": "string", "description": "Brief description of what the command does and why." },
                        "is_background": { "type": "boolean", "description": "Run in the background and return immediately with a task id.", "default": false },
                        "timeout_ms": { "type": "integer", "description": "Timeout in milliseconds (default 300000 = 5 min). Use small values for risky probes.", "default": 300000 },
                        "dir_path": { "type": "string", "description": "Working directory for the command (defaults to the session working directory)." }
                    },
                    "required": ["command"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "manage_background_task",
                "description": "Inspect or stop a background task started with run_shell_command is_background=true.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "action": { "type": "string", "enum": ["status", "logs", "kill", "list"], "description": "status: running/exited; logs: tail combined output; kill: terminate; list: all tasks." },
                        "id": { "type": "integer", "description": "Background task id (omit for list)." },
                        "lines": { "type": "integer", "description": "How many trailing log lines to return (default 60).", "default": 60 }
                    },
                    "required": ["action"]
                }
            }
        }
    ])
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn full_agent_profile_exposes_all_tools() {
        let names = tool_names(&tools_for(ToolProfile::Full, Mode::Agent, &[]));
        for expected in [
            "analyze_image", "edit_file", "explore", "manage_background_task", "read_file",
            "read_url", "run_shell_command", "update_plan", "web_search", "write_file",
        ] {
            assert!(names.contains(&expected.to_string()), "missing {expected} in {names:?}");
        }
    }

    #[test]
    fn readonly_profile_is_read_file_and_shell_only() {
        let names = tool_names(&tools_for(ToolProfile::ReadOnly, Mode::Agent, &[]));
        assert_eq!(names, vec!["read_file", "run_shell_command"]);
    }

    #[test]
    fn readonly_profile_drops_mcp_tools() {
        let mcp = vec![json!({
            "type": "function",
            "function": { "name": "mcp__srv__thing", "parameters": { "type": "object" } }
        })];
        let names = tool_names(&tools_for(ToolProfile::ReadOnly, Mode::Agent, &mcp));
        assert_eq!(names, vec!["read_file", "run_shell_command"]);
        // ...but Full/Agent keeps them.
        let full = tool_names(&tools_for(ToolProfile::Full, Mode::Agent, &mcp));
        assert!(full.contains(&"mcp__srv__thing".to_string()));
    }

    #[test]
    fn plan_mode_tool_list_is_exact() {
        let names = tool_names(&tools_for(ToolProfile::Full, Mode::Plan, &[]));
        assert_eq!(names, vec!["explore", "read_file", "update_plan"]);
    }

    #[test]
    fn research_mode_tool_list_unchanged() {
        let names = tool_names(&tools_for(ToolProfile::Full, Mode::Research, &[]));
        assert_eq!(names, vec!["read_url", "update_plan", "web_search"]);
    }
}
