use crate::api::Tool;
use serde_json::json;

fn read_file_tool() -> Tool {
    Tool::Regular {
        name: "read_file".to_string(),
        description: "Read the contents of a file. Works within the workspace by default. Can also read files outside the workspace — the user will be prompted to allow access if not already configured.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the file (e.g., 'src/main.rs'). Can also be absolute or ~/ paths for external files (user will be prompted for Read access)."
                }
            },
            "required": ["path"]
        }),
        cache_control: None,
    }
}

fn write_file_tool(has_morph: bool) -> Tool {
    let base = if has_morph {
        "Create a new file with the given content. For editing existing files, use morph_edit_file instead."
    } else {
        "Create a new file or overwrite an existing file with the given content."
    };
    let description = format!(
        "{base} Works within the workspace by default; can also write to external absolute or \
         ~/ paths (user will be prompted for Write access). \
         For files too large to emit in a single response, set `append: true` on subsequent \
         calls — the first call (append=false or omitted) creates/overwrites, and each \
         later call appends. This lets you split a large document into multiple \
         tool calls without hitting `max_output_tokens`."
    );

    Tool::Regular {
        name: "write_file".to_string(),
        description,
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the file (e.g., 'src/main.rs'). Can also be absolute or ~/ paths for external files (user will be prompted for Write access)."
                },
                "content": {
                    "type": "string",
                    "description": "The content to write (or append) to the file."
                },
                "append": {
                    "type": "boolean",
                    "description": "If true, append `content` to an existing file (creating it if missing) instead of overwriting. Use this to write a large document across several calls, each carrying a chunk of the final file.",
                    "default": false
                }
            },
            "required": ["path", "content"]
        }),
        cache_control: None,
    }
}

fn list_directory_tool() -> Tool {
    Tool::Regular {
        name: "list_directory".to_string(),
        description: "List files and directories in a single directory (non-recursive). Use this to explore a specific folder's contents. For finding files across multiple directories by pattern, use glob_files instead.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the directory (e.g., 'src' or '.'). Can also be absolute or ~/ paths for external directories (user will be prompted for Read access)."
                }
            },
            "required": ["path"]
        }),
        cache_control: None,
    }
}

fn create_directory_tool() -> Tool {
    Tool::Regular {
        name: "create_directory".to_string(),
        description: "Create a new directory (and parent directories if needed). Supports absolute or ~/ paths for external directories (user will be prompted for Write access).".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the directory (e.g., 'src/utils'). Can also be absolute or ~/ paths for external directories (user will be prompted for Write access)."
                }
            },
            "required": ["path"]
        }),
        cache_control: None,
    }
}

fn anthropic_web_search_tool() -> Tool {
    Tool::AnthropicWebSearch {
        tool_type: "web_search_20250305".to_string(),
        name: "web_search".to_string(),
        max_uses: Some(5),
        allowed_domains: None,
        blocked_domains: None,
        cache_control: None,
    }
}

fn openai_web_search_tool() -> Tool {
    Tool::OpenAIWebSearch {
        tool_type: "web_search".to_string(),
    }
}

fn execute_bash_tool() -> Tool {
    Tool::Regular {
        name: "execute_bash".to_string(),
        description: "Execute a bash command in the workspace. Commands can reference external absolute or ~/ paths — the user will be prompted for Bash path access. Parent directory traversal (..) is always blocked. Never run destructive or irreversible shell commands (e.g., rm -rf, rm, rmdir, dd, mkfs*, fdisk/parted, wipefs, chmod/chown -R on broad paths, truncate, :>, >/dev/sd*, kill -9 on system services). Prefer read-only commands and dry-runs; if a potentially destructive action seems necessary, stop and request explicit confirmation before proceeding.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "The bash command to execute (e.g., 'cargo test', 'ls -la', 'cat /path/to/file')"
                }
            },
            "required": ["command"]
        }),
        cache_control: None,
    }
}

fn delete_file_tool() -> Tool {
    Tool::Regular {
        name: "delete_file".to_string(),
        description: "Delete a file in the current workspace. A confirmation prompt will be shown to the user before deletion.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the file to delete (e.g., 'src/old_file.rs')"
                }
            },
            "required": ["path"]
        }),
        cache_control: None,
    }
}

fn delete_directory_tool() -> Tool {
    Tool::Regular {
        name: "delete_directory".to_string(),
        description: "Delete a directory and all its contents in the current workspace. A confirmation prompt will be shown to the user before deletion.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the directory to delete (e.g., 'src/old_module')"
                }
            },
            "required": ["path"]
        }),
        cache_control: None,
    }
}

fn move_file_tool() -> Tool {
    Tool::Regular {
        name: "move_file".to_string(),
        description: "Move or rename a file or directory. Creates parent directories if needed. Supports absolute or ~/ paths for either endpoint (user will be prompted for Write access on external paths, since moving removes the source).".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "source": {
                    "type": "string",
                    "description": "The relative path to the source file/directory (e.g., 'src/old_name.rs'). Can also be absolute or ~/ paths; external sources require a Write grant (move removes the source)."
                },
                "destination": {
                    "type": "string",
                    "description": "The relative path to the destination (e.g., 'src/new_name.rs' or 'new_folder/file.rs'). Can also be absolute or ~/ paths; external destinations require a Write grant."
                }
            },
            "required": ["source", "destination"]
        }),
        cache_control: None,
    }
}

fn copy_file_tool() -> Tool {
    Tool::Regular {
        name: "copy_file".to_string(),
        description: "Copy a file to a new location. Creates parent directories if needed. Supports absolute or ~/ paths for either endpoint (user will be prompted for Read on external sources and Write on external destinations).".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "source": {
                    "type": "string",
                    "description": "The relative path to the source file (e.g., 'src/template.rs'). Can also be absolute or ~/ paths; external sources require a Read grant."
                },
                "destination": {
                    "type": "string",
                    "description": "The relative path to the destination (e.g., 'src/new_file.rs'). Can also be absolute or ~/ paths; external destinations require a Write grant."
                }
            },
            "required": ["source", "destination"]
        }),
        cache_control: None,
    }
}

fn edit_file_tool() -> Tool {
    Tool::Regular {
        name: "edit_file".to_string(),
        description: "Make targeted edits to a file by replacing exact string matches. Preferred over write_file for modifying existing files — safer and more efficient since only the changed portion is specified. Works within the workspace by default; can also edit external files at absolute or ~/ paths (user will be prompted for Write access).".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "The relative path to the file (e.g., 'src/main.rs'). Can also be absolute or ~/ paths for external files."
                },
                "old_string": {
                    "type": "string",
                    "description": "The exact text to find and replace. Must match the file content exactly, including whitespace and indentation."
                },
                "new_string": {
                    "type": "string",
                    "description": "The replacement text. Use an empty string to delete the matched text."
                },
                "replace_all": {
                    "type": "boolean",
                    "description": "If true, replace all occurrences. Default: false (replace first match only)."
                }
            },
            "required": ["path", "old_string", "new_string"]
        }),
        cache_control: None,
    }
}

fn glob_files_tool() -> Tool {
    let excludes = crate::tools::codesearch::default_exclude_dirs_human();
    let tool_description = format!(
        "Find files recursively by glob pattern. Use this when you need to find files across the codebase by name or extension (e.g., '**/*.rs', 'src/**/test_*.py'). For listing a single directory's contents, use list_directory instead. By default skips build/vendored directories ({excludes}) and does not follow symlinks (matching ripgrep's default); set include_ignored=true or follow_symlinks=true to widen the walk."
    );
    let include_ignored_description = format!(
        "When true, descend into the default build/vendored excludes ({excludes}). Default: false. Only set this when you specifically need to find files inside build artefacts or vendored code."
    );

    Tool::Regular {
        name: "glob_files".to_string(),
        description: tool_description,
        input_schema: json!({
            "type": "object",
            "properties": {
                "pattern": {
                    "type": "string",
                    "description": "Glob pattern (e.g., '**/*.rs', 'src/**/mod.rs', '*.toml', 'tests/**/*.py')"
                },
                "path": {
                    "type": "string",
                    "description": "Directory to search in, relative to workspace root. Default: '.' (entire workspace). Can also be absolute or ~/ paths for external directories (user will be prompted for Read access)."
                },
                "include_ignored": {
                    "type": "boolean",
                    "description": include_ignored_description
                },
                "follow_symlinks": {
                    "type": "boolean",
                    "description": "When true, follow symlinks while walking (equivalent to `rg -L`). Default: false — matches ripgrep's default and prevents a workspace-internal symlink that points outside from leaking filenames under the target directory."
                }
            },
            "required": ["pattern"]
        }),
        cache_control: None,
    }
}

fn web_fetch_tool() -> Tool {
    Tool::Regular {
        name: "web_fetch".to_string(),
        description: "Fetch a URL and return its content as readable text. Use this to read documentation pages, API references, or any web content. For searching the web by query, use web_search instead.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "url": {
                    "type": "string",
                    "description": "The URL to fetch (e.g., 'https://docs.rs/serde/latest/serde/')"
                }
            },
            "required": ["url"]
        }),
        cache_control: None,
    }
}

fn morph_edit_file_tool() -> Tool {
    // Schema matches the official Morph Fast Apply tool definition:
    // https://docs.morphllm.com/sdk/components/fast-apply
    // Field names (`target_filepath`, `instructions`, `code_edit`) and the
    // description are kept aligned with Morph's canonical schema so models
    // trained on it call the tool consistently across providers.
    Tool::Regular {
        name: "morph_edit_file".to_string(),
        description: "Edit an existing file by showing only the changed lines. \
            Use // ... existing code ... to represent unchanged sections. \
            Include just enough surrounding context to locate each edit precisely. \
            ALWAYS use the marker for unchanged sections (omitting it will cause deletions). \
            Preserve exact indentation. For deletions, show context before and after. \
            Batch multiple edits to the same file in one call. \
            Backed by the Morph Fast Apply API (10,500+ tokens/sec). \
            Supports absolute or ~/ paths for files outside the workspace \
            (user will be prompted for Write access)."
            .to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "target_filepath": {
                    "type": "string",
                    "description": "Path of the file to modify"
                },
                "instructions": {
                    "type": "string",
                    "description": "A single sentence written in the first person describing what the agent is changing. Used to help disambiguate uncertainty in the edit."
                },
                "code_edit": {
                    "type": "string",
                    "description": "Specify ONLY the precise lines of code that you wish to edit. Use // ... existing code ... for unchanged sections."
                }
            },
            "required": ["target_filepath", "instructions", "code_edit"]
        }),
        cache_control: None,
    }
}

/// Get available tools for Claude/GPT API
pub fn get_all_tools() -> Vec<Tool> {
    vec![
        list_directory_tool(),
        read_file_tool(),
        write_file_tool(false),
        edit_file_tool(),
        glob_files_tool(),
        create_directory_tool(),
        delete_file_tool(),
        delete_directory_tool(),
        move_file_tool(),
        copy_file_tool(),
        execute_bash_tool(),
        web_fetch_tool(),
        anthropic_web_search_tool(),
        openai_web_search_tool(),
    ]
}

pub fn get_all_tools_with_morph() -> Vec<Tool> {
    vec![
        list_directory_tool(),
        read_file_tool(),
        write_file_tool(true),
        edit_file_tool(),
        glob_files_tool(),
        create_directory_tool(),
        delete_file_tool(),
        delete_directory_tool(),
        move_file_tool(),
        copy_file_tool(),
        execute_bash_tool(),
        morph_edit_file_tool(),
        web_fetch_tool(),
        anthropic_web_search_tool(),
        openai_web_search_tool(),
    ]
}

pub fn get_read_only_tools() -> Vec<Tool> {
    vec![
        list_directory_tool(),
        read_file_tool(),
        glob_files_tool(),
        web_fetch_tool(),
        // Anthropic web search tool
        anthropic_web_search_tool(),
        // OpenAI web search tool
        openai_web_search_tool(),
    ]
}

/// Add code search tool to an existing tool list
pub fn add_code_search_tool(tools: &mut Vec<Tool>) {
    let excludes = crate::tools::codesearch::default_exclude_dirs_human();
    let tool_description = format!(
        "Search for patterns in code using ripgrep. Supports regex patterns and file type filtering. Fast search across the entire codebase. By default skips build/vendored directories ({excludes}) and respects .gitignore; set include_ignored=true to search everywhere."
    );
    let max_results_description = format!(
        "Maximum results per file (default: {})",
        crate::tools::codesearch::DEFAULT_MAX_RESULTS_PER_FILE
    );
    let include_ignored_description = format!(
        "When true, bypass the default build/vendored excludes ({excludes}) and ignore files (.gitignore, .ignore). Default: false. Only set this when you specifically need to grep inside build artefacts or vendored code."
    );

    tools.push(Tool::Regular {
        name: "search_code".to_string(),
        description: tool_description,
        input_schema: json!({
            "type": "object",
            "properties": {
                "pattern": {
                    "type": "string",
                    "description": "The search pattern (regex supported, e.g., 'fn main', 'struct.*User')"
                },
                "file_type": {
                    "type": "string",
                    "description": "Optional file type filter (e.g., 'rust', 'py', 'js', 'ts'). See ripgrep docs for available types."
                },
                "max_results": {
                    "type": "integer",
                    "description": max_results_description
                },
                "include_ignored": {
                    "type": "boolean",
                    "description": include_ignored_description
                }
            },
            "required": ["pattern"]
        }),
        cache_control: None,
    });
}

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

    #[test]
    fn test_regular_tool_serialization() {
        let tool = read_file_tool();
        let serialized = serde_json::to_value(&tool).expect("Failed to serialize regular tool");

        // Regular tools should have name, description, and input_schema
        assert_eq!(serialized["name"], "read_file");
        assert!(serialized.get("description").is_some());
        assert!(serialized.get("input_schema").is_some());

        // Regular tools should NOT have a type field at the root
        assert!(serialized.get("type").is_none());
    }

    #[test]
    fn test_anthropic_web_search_tool_serialization() {
        let tool = anthropic_web_search_tool();
        let serialized = serde_json::to_value(&tool).expect("Failed to serialize web search tool");

        // Verify the correct structure for Claude's server-side web search
        assert_eq!(serialized["type"], "web_search_20250305");
        assert_eq!(serialized["name"], "web_search");
        assert_eq!(serialized["max_uses"], 5);

        // Ensure it has the correct type identifier
        assert!(serialized.get("type").is_some());
    }

    #[test]
    fn test_openai_web_search_tool_serialization() {
        let tool = openai_web_search_tool();
        let serialized = serde_json::to_value(&tool).expect("Failed to serialize web search tool");

        // Verify the correct structure for OpenAI's server-side web search
        assert_eq!(serialized["type"], "web_search");

        // Ensure it has the correct type identifier
        assert!(serialized.get("type").is_some());
    }
}
