//! MCP tool definitions (JSON Schema descriptors).
//!
//! Each `def_*` function returns a `ToolDefinition` with the tool name,
//! description, JSON Schema for its input parameters, MCP annotations
//! (readOnlyHint, title), and optional `_meta` (anthropic/alwaysLoad).

use serde_json::{json, Value};

use super::ToolDefinition;

/// Read-only annotations shared by every tool.
fn read_only(title: &str) -> Value {
    json!({
        "readOnlyHint": true,
        "title": title
    })
}

/// Build a `ToolDefinition` with `readOnlyHint` annotation and no `_meta`.
fn def(name: &str, title: &str, description: &str, input_schema: Value) -> ToolDefinition {
    ToolDefinition {
        name: name.to_string(),
        description: description.to_string(),
        input_schema,
        annotations: Some(read_only(title)),
        meta: None,
    }
}

/// Build a `ToolDefinition` with `readOnlyHint` AND `anthropic/alwaysLoad`.
fn def_always_load(
    name: &str,
    title: &str,
    description: &str,
    input_schema: Value,
) -> ToolDefinition {
    ToolDefinition {
        name: name.to_string(),
        description: description.to_string(),
        input_schema,
        annotations: Some(read_only(title)),
        meta: Some(json!({ "anthropic/alwaysLoad": true })),
    }
}

/// Computes the call budget based on project size.
pub fn explore_call_budget(total_nodes: u64) -> u8 {
    match total_nodes {
        0..=5_000 => 3,
        5_001..=20_000 => 4,
        20_001..=80_000 => 5,
        80_001..=250_000 => 7,
        _ => 10,
    }
}

/// Generates the `tokensave_context` description with a dynamic call budget.
pub fn context_description(node_count: u64, budget: u8) -> String {
    format!(
        "Build an AI-ready context for a task description. Returns relevant symbols, \
         relationships, and optionally code snippets.\n\n\
         CALL BUDGET: {budget} calls maximum for this project ({node_count} nodes). \
         Stop after {budget} calls. If the question is not fully answered, synthesise \
         from what you have — do not exceed the budget."
    )
}

/// Returns tool definitions with a dynamic call budget for `tokensave_context`.
pub fn get_tool_definitions_with_budget(node_count: u64, budget: u8) -> Vec<ToolDefinition> {
    let mut defs = get_tool_definitions();
    // Replace the context tool's description with the budgeted version
    for def in &mut defs {
        if def.name == "tokensave_context" {
            def.description = context_description(node_count, budget);
        }
    }
    defs
}

/// Returns the list of all tool definitions exposed by this MCP server.
///
/// Tools whose backing dependency is missing on the current host are
/// filtered out so the model never sees a tool that will immediately
/// fail when called. Currently this only affects `tokensave_ast_grep_rewrite`,
/// which shells out to the `ast-grep` binary.
pub fn get_tool_definitions() -> Vec<ToolDefinition> {
    let mut definitions = vec![
        def_search(),
        def_context(),
        def_callers(),
        def_callees(),
        def_impact(),
        def_node(),
        def_status(),
        def_files(),
        def_affected(),
        def_dead_code(),
        def_diff_context(),
        def_module_api(),
        def_circular(),
        def_hotspots(),
        def_similar(),
        def_rename_preview(),
        def_unused_imports(),
        def_rank(),
        def_largest(),
        def_coupling(),
        def_inheritance_depth(),
        def_distribution(),
        def_recursion(),
        def_complexity(),
        def_doc_coverage(),
        def_god_class(),
        def_changelog(),
        def_port_status(),
        def_port_order(),
        def_commit_context(),
        def_pr_context(),
        def_simplify_scan(),
        def_test_map(),
        def_type_hierarchy(),
        def_branch_search(),
        def_branch_diff(),
        def_branch_list(),
        def_str_replace(),
        def_multi_str_replace(),
        def_insert_at(),
        def_ast_grep_rewrite(),
        def_gini(),
        def_dependency_depth(),
        def_health(),
        def_dsm(),
        def_test_risk(),
        def_session_start(),
        def_session_end(),
        def_body(),
        def_todos(),
        def_callers_for(),
        def_by_qualified_name(),
        def_signature(),
        def_impls(),
        def_diagnose(),
        def_derives(),
        def_run_affected_tests(),
        def_record_decision(),
        def_record_code_area(),
        def_session_recall(),
        def_read(),
        def_outline(),
        def_implementations(),
        def_unsafe_patterns(),
        def_diagnostics(),
        def_config(),
        def_signature_search(),
        def_constructors(),
        def_field_sites(),
    ];
    if !ast_grep_available() {
        definitions.retain(|d| d.name != "tokensave_ast_grep_rewrite");
    }
    debug_assert!(
        !definitions.is_empty(),
        "get_tool_definitions returned empty list"
    );
    debug_assert!(
        definitions.iter().all(|d| d.name.starts_with("tokensave_")),
        "all tool definitions must have 'tokensave_' prefix"
    );
    definitions
}

/// Returns true when the external `ast-grep` binary is on PATH. Result is
/// cached after the first check so we don't fork a subprocess on every
/// `tools/list` request.
pub fn ast_grep_available() -> bool {
    use std::sync::OnceLock;
    static AVAILABLE: OnceLock<bool> = OnceLock::new();
    *AVAILABLE.get_or_init(|| {
        std::process::Command::new("ast-grep")
            .arg("--version")
            .output()
            .is_ok_and(|out| out.status.success())
    })
}

// ── alwaysLoad tools (loaded into the model prompt immediately) ─────────

fn def_search() -> ToolDefinition {
    def_always_load(
        "tokensave_search",
        "Search Symbols",
        "Search for symbols (functions, structs, traits, etc.) in the code graph by name or keyword.",
        json!({
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search query string to match against symbol names"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            },
            "required": ["query"]
        }),
    )
}

fn def_context() -> ToolDefinition {
    def_always_load(
        "tokensave_context",
        "Task Context",
        &context_description(0, 3),
        json!({
            "type": "object",
            "properties": {
                "task": {
                    "type": "string",
                    "description": "Natural language description of the task or question"
                },
                "max_nodes": {
                    "type": "number",
                    "description": "Maximum number of symbols to include (default: 20)"
                },
                "include_code": {
                    "type": "boolean",
                    "description": "If true, include source code snippets for key symbols (default: false)"
                },
                "max_code_blocks": {
                    "type": "number",
                    "description": "Maximum number of code snippets when include_code is true (default: 5)"
                },
                "mode": {
                    "type": "string",
                    "enum": ["explore", "plan"],
                    "description": "Context mode: 'explore' (default) for general exploration, 'plan' for implementation planning (adds extension points, dependency order, test coverage)"
                },
                "keywords": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Extra search keywords for synonym expansion. Use this when the task uses conceptual terms that may not match symbol names — e.g. for 'authentication', pass [\"login\", \"session\", \"credential\", \"token\", \"auth\"]. The graph is searched for each keyword independently."
                },
                "exclude_node_ids": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Node IDs to exclude from results (pass seen_node_ids from previous call for session deduplication)"
                },
                "merge_adjacent": {
                    "type": "boolean",
                    "description": "When true, merge code blocks from the same file whose line ranges are adjacent or overlapping (default: false)"
                },
                "max_per_file": {
                    "type": "number",
                    "description": "Maximum symbols from a single file in results. Prevents one large file from dominating (default: max_nodes/3, minimum 3)"
                }
            },
            "required": ["task"]
        }),
    )
}

fn def_status() -> ToolDefinition {
    def_always_load(
        "tokensave_status",
        "Graph Status",
        "Return aggregate statistics about the code graph (node/edge/file counts, DB size, etc.).",
        json!({
            "type": "object",
            "properties": {}
        }),
    )
}

fn def_callers_for() -> ToolDefinition {
    def(
        "tokensave_callers_for",
        "Bulk callers",
        "Returns the caller set of every supplied node ID in one round-trip. \
         Useful for clustering or similarity queries that need many caller \
         sets at once. Returns a map of {node_id: [caller_id, …]}. Defaults \
         to `calls` edges; pass `kind` to filter by `uses`, `type_of`, etc.",
        json!({
            "type": "object",
            "properties": {
                "node_ids": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Node IDs to look up callers for."
                },
                "kind": {
                    "type": "string",
                    "description": "Edge kind to filter by (default: \"calls\"). Pass an empty string to match all kinds."
                },
                "max_per_item": {
                    "type": "number",
                    "description": "Cap callers per item (default: 1000)."
                }
            },
            "required": ["node_ids"]
        }),
    )
}

fn def_by_qualified_name() -> ToolDefinition {
    def(
        "tokensave_by_qualified_name",
        "Lookup by qualified name",
        "Look up nodes by their qualified name. Multiple rows can share a \
         qualified name (overloads, generics, separate impl blocks). Useful \
         for cross-run lookups where the content-hash node ID has changed.",
        json!({
            "type": "object",
            "properties": {
                "qualified_name": {
                    "type": "string",
                    "description": "The exact qualified name to look up."
                }
            },
            "required": ["qualified_name"]
        }),
    )
}

fn def_impls() -> ToolDefinition {
    def(
        "tokensave_impls",
        "Trait Implementations",
        "List `impl` blocks matching a trait, a type, or both. With no filter \
         returns every impl in the graph (use sparingly). Both arguments \
         accept short names (e.g. `Display`) or qualified names. Surfaces \
         information that is otherwise hard to query: trait-method dispatch \
         targets, which types satisfy a given trait, and which traits a type \
         implements.",
        json!({
            "type": "object",
            "properties": {
                "trait": {
                    "type": "string",
                    "description": "Trait name to filter by (short or qualified). Omit to include all traits."
                },
                "type": {
                    "type": "string",
                    "description": "Implementing type to filter by (short or qualified). Omit to include all types."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 100)."
                }
            }
        }),
    )
}

fn def_signature() -> ToolDefinition {
    def(
        "tokensave_signature",
        "Signature",
        "Return the signature-level metadata for symbols matching a qualified \
         name — visibility, signature string (generics, params, return type, \
         where clauses), docstring, async flag, and kind. No bodies. Use this \
         instead of reading source files when you only need the public-API \
         surface of a function, method, or type. Multiple rows can be \
         returned (overloads, separate impls).",
        json!({
            "type": "object",
            "properties": {
                "qualified_name": {
                    "type": "string",
                    "description": "The exact qualified name to look up."
                },
                "node_id": {
                    "type": "string",
                    "description": "Optional: look up a single node by its ID instead of qualified_name."
                }
            }
        }),
    )
}

// ── Deferred tools (discovered via ToolSearch on demand) ────────────────

fn def_callers() -> ToolDefinition {
    def(
        "tokensave_callers",
        "Callers",
        "Find all callers of a given node (function, method, etc.) up to a specified depth.",
        json!({
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "string",
                    "description": "The unique node ID to find callers for"
                },
                "max_depth": {
                    "type": "number",
                    "description": "Maximum traversal depth (default: 3)"
                }
            },
            "required": ["node_id"]
        }),
    )
}

fn def_callees() -> ToolDefinition {
    def(
        "tokensave_callees",
        "Callees",
        "Find all callees of a given node (function, method, etc.) up to a \
         specified depth. When a callee resolves to a trait method, the \
         concrete impl methods reachable through that trait are also \
         returned, tagged with `dispatch_via_trait: true` and a `dispatch_from` \
         pointing at the trait method. Pass `resolve_dispatch: false` to \
         disable this behaviour and get only direct call edges.",
        json!({
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "string",
                    "description": "The unique node ID to find callees for"
                },
                "max_depth": {
                    "type": "number",
                    "description": "Maximum traversal depth (default: 3)"
                },
                "resolve_dispatch": {
                    "type": "boolean",
                    "description": "If true (default), append concrete impl methods for any trait-method callee."
                }
            },
            "required": ["node_id"]
        }),
    )
}

fn def_impact() -> ToolDefinition {
    def(
        "tokensave_impact",
        "Impact Radius",
        "Compute the impact radius of a node: all symbols that directly or indirectly depend on it.",
        json!({
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "string",
                    "description": "The unique node ID to compute impact for"
                },
                "max_depth": {
                    "type": "number",
                    "description": "Maximum traversal depth (default: 3)"
                }
            },
            "required": ["node_id"]
        }),
    )
}

fn def_node() -> ToolDefinition {
    def(
        "tokensave_node",
        "Node Details",
        "Retrieve detailed information about a single node by its ID.",
        json!({
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "string",
                    "description": "The unique node ID to retrieve"
                }
            },
            "required": ["node_id"]
        }),
    )
}

fn def_files() -> ToolDefinition {
    def(
        "tokensave_files",
        "File List",
        "List indexed project files. Use to explore file structure without reading file contents.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path"
                },
                "pattern": {
                    "type": "string",
                    "description": "Filter files matching this glob pattern (e.g. '**/*.rs')"
                },
                "format": {
                    "type": "string",
                    "enum": ["flat", "grouped"],
                    "description": "Output format: flat (one per line) or grouped by directory (default: grouped)"
                }
            }
        }),
    )
}

fn def_affected() -> ToolDefinition {
    def(
        "tokensave_affected",
        "Affected Tests",
        "Find test files affected by changed source files via dependency graph traversal.",
        json!({
            "type": "object",
            "properties": {
                "files": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "List of changed file paths to analyze"
                },
                "depth": {
                    "type": "number",
                    "description": "Maximum dependency traversal depth (default: 5)"
                },
                "filter": {
                    "type": "string",
                    "description": "Custom glob pattern for test files (default: common test patterns)"
                }
            },
            "required": ["files"]
        }),
    )
}

fn def_dead_code() -> ToolDefinition {
    def(
        "tokensave_dead_code",
        "Dead Code",
        "Find symbols with no incoming edges (potentially unreachable code). \
         Always excludes `main` and `test*` functions. By default also excludes \
         `pub` items (they may be referenced outside the indexed scope) — pass \
         `include_public: true` to audit pub items with zero indexed callers, \
         which is what you want for workspace-internal cleanup.",
        json!({
            "type": "object",
            "properties": {
                "kinds": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Node kinds to check (default: [\"function\", \"method\"])"
                },
                "include_public": {
                    "type": "boolean",
                    "description": "When true, do NOT exclude pub items. Default false."
                }
            }
        }),
    )
}

fn def_diff_context() -> ToolDefinition {
    def(
        "tokensave_diff_context",
        "Diff Context",
        "Given changed file paths, return semantic context: which symbols were modified, what depends on them, and affected tests.",
        json!({
            "type": "object",
            "properties": {
                "files": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "List of changed file paths"
                },
                "depth": {
                    "type": "number",
                    "description": "Maximum impact traversal depth (default: 2)"
                }
            },
            "required": ["files"]
        }),
    )
}

fn def_module_api() -> ToolDefinition {
    def(
        "tokensave_module_api",
        "Module API",
        "Show the public API surface of a file or directory: all pub symbols sorted by file and line.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "File path or directory prefix to inspect"
                }
            },
            "required": ["path"]
        }),
    )
}

fn def_circular() -> ToolDefinition {
    def(
        "tokensave_circular",
        "Circular Deps",
        "Detect circular dependencies between files in the code graph.",
        json!({
            "type": "object",
            "properties": {
                "max_depth": {
                    "type": "number",
                    "description": "Maximum cycle detection depth (default: 10)"
                }
            }
        }),
    )
}

fn def_hotspots() -> ToolDefinition {
    def(
        "tokensave_hotspots",
        "Hotspots",
        "Find symbols with the highest connectivity (most incoming + outgoing edges).",
        json!({
            "type": "object",
            "properties": {
                "limit": {
                    "type": "number",
                    "description": "Maximum number of hotspots to return (default: 10)"
                }
            }
        }),
    )
}

fn def_similar() -> ToolDefinition {
    def(
        "tokensave_similar",
        "Similar Symbols",
        "Find symbols with similar names using full-text search and substring matching.",
        json!({
            "type": "object",
            "properties": {
                "symbol": {
                    "type": "string",
                    "description": "Symbol name to find similar matches for"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results (default: 10)"
                }
            },
            "required": ["symbol"]
        }),
    )
}

fn def_rename_preview() -> ToolDefinition {
    def(
        "tokensave_rename_preview",
        "References",
        "Show all references to a symbol -- all edges where the node appears as source or target.",
        json!({
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "string",
                    "description": "The unique node ID to find references for"
                }
            },
            "required": ["node_id"]
        }),
    )
}

fn def_unused_imports() -> ToolDefinition {
    def(
        "tokensave_unused_imports",
        "Unused Imports",
        "Find import/use nodes that are never referenced by any other node.",
        json!({
            "type": "object",
            "properties": {}
        }),
    )
}

fn def_rank() -> ToolDefinition {
    def(
        "tokensave_rank",
        "Rank",
        "Rank nodes by edge count for a given relationship type (calls, implements, extends, etc.).",
        json!({
            "type": "object",
            "properties": {
                "edge_kind": {
                    "type": "string",
                    "enum": ["implements", "extends", "calls", "uses", "contains", "annotates", "derives_macro"],
                    "description": "The relationship type to rank by (e.g. 'implements' to find most-implemented interfaces)"
                },
                "direction": {
                    "type": "string",
                    "enum": ["incoming", "outgoing"],
                    "description": "Edge direction: 'incoming' ranks targets (default, e.g. most-implemented interface), 'outgoing' ranks sources (e.g. class that implements the most interfaces)"
                },
                "node_kind": {
                    "type": "string",
                    "description": "Optional filter for node kind (e.g. 'interface', 'class', 'trait', 'function', 'method')"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            },
            "required": ["edge_kind"]
        }),
    )
}

fn def_largest() -> ToolDefinition {
    def(
        "tokensave_largest",
        "Largest Symbols",
        "Rank nodes by size (line count). Find the largest classes, longest methods, biggest enums, etc.",
        json!({
            "type": "object",
            "properties": {
                "node_kind": {
                    "type": "string",
                    "description": "Filter by node kind (e.g. 'class', 'method', 'function', 'interface', 'enum', 'struct')"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            }
        }),
    )
}

fn def_coupling() -> ToolDefinition {
    def(
        "tokensave_coupling",
        "Coupling",
        "Rank files by coupling: fan_in (most depended on) or fan_out (most dependencies).",
        json!({
            "type": "object",
            "properties": {
                "direction": {
                    "type": "string",
                    "enum": ["fan_in", "fan_out"],
                    "description": "fan_in: files depended on by the most others. fan_out: files that depend on the most others (default: fan_in)"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            }
        }),
    )
}

fn def_inheritance_depth() -> ToolDefinition {
    def(
        "tokensave_inheritance_depth",
        "Inheritance Depth",
        "Find the deepest class/interface inheritance hierarchies by walking extends chains.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            }
        }),
    )
}

fn def_distribution() -> ToolDefinition {
    def(
        "tokensave_distribution",
        "Distribution",
        "Show node kind distribution (classes, methods, fields, etc.) per file or directory.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Directory or file path prefix to filter (e.g. 'src/main/java/com/example'). Omit for entire codebase."
                },
                "summary": {
                    "type": "boolean",
                    "description": "If true, aggregate counts across all matching files instead of per-file breakdown (default: false)"
                }
            }
        }),
    )
}

fn def_recursion() -> ToolDefinition {
    def(
        "tokensave_recursion",
        "Recursion",
        "Detect recursive and mutually-recursive call cycles in the call graph.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of cycles to return (default: 10)"
                }
            }
        }),
    )
}

fn def_complexity() -> ToolDefinition {
    def(
        "tokensave_complexity",
        "Complexity",
        "Rank functions/methods by composite complexity score (lines + fan-out + fan-in).",
        json!({
            "type": "object",
            "properties": {
                "node_kind": {
                    "type": "string",
                    "description": "Filter by node kind (default: function and method)"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            }
        }),
    )
}

fn def_doc_coverage() -> ToolDefinition {
    def(
        "tokensave_doc_coverage",
        "Doc Coverage",
        "Find public symbols missing documentation (docstrings).",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Directory or file path prefix to filter (e.g. 'src/main'). Omit for entire codebase."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 50)"
                }
            }
        }),
    )
}

fn def_god_class() -> ToolDefinition {
    def(
        "tokensave_god_class",
        "God Classes",
        "Find classes with the most members (methods + fields).",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (e.g. 'src/main/java')"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            }
        }),
    )
}

fn def_changelog() -> ToolDefinition {
    def(
        "tokensave_changelog",
        "Changelog",
        "Generate a semantic diff/changelog between two git refs, categorizing symbols as added, removed, or modified.",
        json!({
            "type": "object",
            "properties": {
                "from_ref": {
                    "type": "string",
                    "description": "Starting git ref (commit, branch, tag)"
                },
                "to_ref": {
                    "type": "string",
                    "description": "Ending git ref (commit, branch, tag)"
                }
            },
            "required": ["from_ref", "to_ref"]
        }),
    )
}

fn def_port_status() -> ToolDefinition {
    def(
        "tokensave_port_status",
        "Port Status",
        "Compare symbols between source and target directories to track porting progress.",
        json!({
            "type": "object",
            "properties": {
                "source_dir": {
                    "type": "string",
                    "description": "Path prefix for source code (e.g. 'src/python/')"
                },
                "target_dir": {
                    "type": "string",
                    "description": "Path prefix for target code (e.g. 'src/rust/')"
                },
                "kinds": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Node kinds to compare (default: [\"function\", \"method\", \"class\", \"struct\", \"interface\", \"trait\", \"enum\", \"module\"])"
                }
            },
            "required": ["source_dir", "target_dir"]
        }),
    )
}

fn def_port_order() -> ToolDefinition {
    def(
        "tokensave_port_order",
        "Port Order",
        "Topological sort of symbols in a directory -- port leaves first, dependents after.",
        json!({
            "type": "object",
            "properties": {
                "source_dir": {
                    "type": "string",
                    "description": "Path prefix for source code (e.g. 'src/python/')"
                },
                "kinds": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Node kinds to include (default: [\"function\", \"method\", \"class\", \"struct\", \"interface\", \"trait\", \"enum\", \"module\"])"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of symbols to return (default: 50)"
                }
            },
            "required": ["source_dir"]
        }),
    )
}

fn def_commit_context() -> ToolDefinition {
    def(
        "tokensave_commit_context",
        "Commit Context",
        "Semantic summary of uncommitted changes for drafting a commit message. Returns changed symbols, file roles, and recent commit style.",
        json!({
            "type": "object",
            "properties": {
                "staged_only": {
                    "type": "boolean",
                    "description": "If true, only analyze staged changes (default: false = all uncommitted changes)"
                }
            }
        }),
    )
}

fn def_pr_context() -> ToolDefinition {
    def(
        "tokensave_pr_context",
        "PR Context",
        "Semantic summary of changes between two git refs for drafting a pull request description.",
        json!({
            "type": "object",
            "properties": {
                "base_ref": {
                    "type": "string",
                    "description": "Base branch or ref to compare against (default: 'main')"
                },
                "head_ref": {
                    "type": "string",
                    "description": "Head branch or ref (default: 'HEAD')"
                }
            }
        }),
    )
}

fn def_simplify_scan() -> ToolDefinition {
    def(
        "tokensave_simplify_scan",
        "Simplify Scan",
        "Quality analysis of changed files: duplications, dead code, coupling, and complexity hotspots.",
        json!({
            "type": "object",
            "properties": {
                "files": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Changed file paths to analyze"
                }
            },
            "required": ["files"]
        }),
    )
}

fn def_test_map() -> ToolDefinition {
    def(
        "tokensave_test_map",
        "Test Map",
        "Map source symbols to their test functions. Shows which tests cover which source code.",
        json!({
            "type": "object",
            "properties": {
                "file": {
                    "type": "string",
                    "description": "Source file path to find test coverage for"
                },
                "node_id": {
                    "type": "string",
                    "description": "Specific node ID to find test coverage for (alternative to file)"
                }
            }
        }),
    )
}

fn def_type_hierarchy() -> ToolDefinition {
    def(
        "tokensave_type_hierarchy",
        "Type Hierarchy",
        "Show the full type hierarchy for a trait/interface/class: all implementors and extenders, recursively.",
        json!({
            "type": "object",
            "properties": {
                "node_id": {
                    "type": "string",
                    "description": "The type node ID to build the hierarchy for"
                },
                "max_depth": {
                    "type": "number",
                    "description": "Maximum inheritance depth to traverse (default: 5)"
                }
            },
            "required": ["node_id"]
        }),
    )
}

fn def_branch_search() -> ToolDefinition {
    def(
        "tokensave_branch_search",
        "Cross-Branch Search",
        "Search for symbols in another branch's code graph. Opens the target branch's DB and runs a search query against it.",
        json!({
            "type": "object",
            "properties": {
                "branch": {
                    "type": "string",
                    "description": "Branch name to search in (must be tracked via `tokensave branch add`)"
                },
                "query": {
                    "type": "string",
                    "description": "Search query string to match against symbol names"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 10)"
                }
            },
            "required": ["branch", "query"]
        }),
    )
}

fn def_branch_diff() -> ToolDefinition {
    def(
        "tokensave_branch_diff",
        "Branch Diff",
        "Compare the code graphs of two branches. Shows symbols added, removed, and changed (signature differs) between base and head.",
        json!({
            "type": "object",
            "properties": {
                "base": {
                    "type": "string",
                    "description": "Base branch name (e.g. 'main'). Defaults to the project's default branch."
                },
                "head": {
                    "type": "string",
                    "description": "Head branch name (e.g. 'feature/foo'). Defaults to the current branch."
                },
                "file": {
                    "type": "string",
                    "description": "Optional file path filter — only show diffs for symbols in this file"
                },
                "kind": {
                    "type": "string",
                    "description": "Optional kind filter — only show diffs for this symbol kind (e.g. 'function', 'struct')"
                }
            }
        }),
    )
}

fn def_branch_list() -> ToolDefinition {
    def(
        "tokensave_branch_list",
        "List Tracked Branches",
        "List all tracked branches with their DB sizes, parent branch, and last sync time. Returns an empty list if multi-branch is not active.",
        json!({
            "type": "object",
            "properties": {}
        }),
    )
}

fn def_str_replace() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_str_replace".to_string(),
        description: "Replace a unique string in a file with new content. Fails if the old string is not found or matches more than once. This is the safest edit primitive — use this instead of sed/awk.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Absolute or project-relative file path"
                },
                "old_str": {
                    "type": "string",
                    "description": "Exact string to find and replace. Must match exactly once in the file."
                },
                "new_str": {
                    "type": "string",
                    "description": "Replacement string"
                }
            },
            "required": ["path", "old_str", "new_str"]
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Edit File"
        })),
        meta: None,
    }
}

fn def_multi_str_replace() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_multi_str_replace".to_string(),
        description: "Apply multiple string replacements atomically in a single file. All replacements must match exactly once. If any replacement fails (0 or >1 matches), the entire operation is aborted and no changes are made.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Absolute or project-relative file path"
                },
                "replacements": {
                    "type": "array",
                    "description": "Array of [old_str, new_str] pairs to replace",
                    "items": {
                        "type": "array",
                        "items": {"type": "string"},
                        "minItems": 2,
                        "maxItems": 2
                    }
                }
            },
            "required": ["path", "replacements"]
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Multi-Edit File"
        })),
        meta: None,
    }
}

fn def_insert_at() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_insert_at".to_string(),
        description: "Insert content before or after a unique anchor in a file. The anchor can be a unique string or a 1-indexed line number. Fails if the anchor matches more than one line.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Absolute or project-relative file path"
                },
                "anchor": {
                    "type": "string",
                    "description": "Unique string or line number (1-indexed) to insert at"
                },
                "content": {
                    "type": "string",
                    "description": "Content to insert"
                },
                "before": {
                    "type": "boolean",
                    "description": "If true, insert before the anchor line; if false, insert after (default: false)"
                }
            },
            "required": ["path", "anchor", "content"]
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Insert Into File"
        })),
        meta: None,
    }
}

fn def_gini() -> ToolDefinition {
    def(
        "tokensave_gini",
        "Gini Inequality",
        "Compute inequality (Gini coefficient) for any metric across files or symbols. Detects god files and uneven complexity distribution.",
        json!({
            "type": "object",
            "properties": {
                "metric": {
                    "type": "string",
                    "enum": ["complexity", "lines", "fan_in", "fan_out", "members"],
                    "description": "Metric to measure inequality for (default: complexity)"
                },
                "scope": {
                    "type": "string",
                    "enum": ["file", "symbol"],
                    "description": "Aggregate per file or per symbol (default: file)"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path"
                },
                "limit": {
                    "type": "number",
                    "description": "Number of top outliers to return (default: 10)"
                }
            }
        }),
    )
}

fn def_dependency_depth() -> ToolDefinition {
    def(
        "tokensave_dependency_depth",
        "Dependency Depth",
        "Show the longest file-level dependency chains. Files at the end of long chains are fragile to upstream changes.",
        json!({
            "type": "object",
            "properties": {
                "limit": {
                    "type": "number",
                    "description": "Maximum number of chains to return (default: 10)"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path"
                }
            }
        }),
    )
}

fn def_health() -> ToolDefinition {
    def(
        "tokensave_health",
        "Health Score",
        "Get quality signal (0-10000) with root cause breakdown (acyclicity, depth, equality, redundancy, modularity). Quality signal = geometric mean of 5 dimensions — maximize this ONE number.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path"
                },
                "details": {
                    "type": "boolean",
                    "description": "If true, include full dimension breakdown (default: false)"
                }
            }
        }),
    )
}

fn def_dsm() -> ToolDefinition {
    def(
        "tokensave_dsm",
        "Design Structure Matrix",
        "Get the Design Structure Matrix: file dependency summary showing clusters, density, and layering violations.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path"
                },
                "format": {
                    "type": "string",
                    "enum": ["stats", "clusters", "matrix"],
                    "description": "Output format (default: stats)"
                },
                "max_files": {
                    "type": "number",
                    "description": "Maximum files in matrix format (default: 30)"
                }
            }
        }),
    )
}

fn def_test_risk() -> ToolDefinition {
    def(
        "tokensave_test_risk",
        "Test Risk",
        "Find high-risk source symbols with weak or no test coverage. Risk = (complexity + 1) × (fan_in + 1) × untested_multiplier. Answers: where should the next test go?",
        json!({
            "type": "object",
            "properties": {
                "limit": {
                    "type": "number",
                    "description": "Maximum number of results to return (default: 20)"
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path"
                },
                "include_tested": {
                    "type": "boolean",
                    "description": "Include already-tested functions in results (default: false)"
                }
            }
        }),
    )
}

fn def_derives() -> ToolDefinition {
    def(
        "tokensave_derives",
        "Derives on Type",
        "List `#[derive(...)]` macros attached to a type and the trait + \
         method names each one synthesizes. Prevents dead-end searches for \
         autogenerated symbols (e.g. `.clone()` from `#[derive(Clone)]`). \
         Well-known derives (`Debug`, `Clone`, `Copy`, `Default`, `PartialEq`, \
         `Eq`, `PartialOrd`, `Ord`, `Hash`, `Serialize`, `Deserialize`, \
         `Display`, `Error`) carry full trait + method info; unknown / \
         proc-macro derives surface with `well_known: false` so callers can \
         still see the derive name.",
        json!({
            "type": "object",
            "properties": {
                "qualified_name": {
                    "type": "string",
                    "description": "The type's qualified name (or short name — same lookup as tokensave_by_qualified_name)."
                },
                "node_id": {
                    "type": "string",
                    "description": "Optional: look up the type by node ID instead."
                }
            }
        }),
    )
}

fn def_diagnose() -> ToolDefinition {
    def(
        "tokensave_diagnose",
        "Diagnose Cargo Output",
        "Parse raw `cargo check` / `cargo clippy` stderr text and map each \
         diagnostic to the smallest containing graph node, with callers \
         pre-attached so you can see what the failing code is reachable \
         from. Diagnostics without a `--> file:line:col` span are dropped. \
         Pass the full stderr capture; you do not need to pre-filter.",
        json!({
            "type": "object",
            "properties": {
                "cargo_output": {
                    "type": "string",
                    "description": "Raw stderr text from `cargo check` / `cargo clippy` / `rustc`."
                },
                "severity": {
                    "type": "string",
                    "enum": ["error", "warning", "all"],
                    "description": "Filter by severity (default: all)."
                },
                "include_callers": {
                    "type": "boolean",
                    "description": "Attach up to 5 callers per diagnostic (default: true)."
                },
                "max_diagnostics": {
                    "type": "number",
                    "description": "Cap on diagnostics in the response (default: 50)."
                }
            },
            "required": ["cargo_output"]
        }),
    )
}

fn def_run_affected_tests() -> ToolDefinition {
    def(
        "tokensave_run_affected_tests",
        "Run Affected Tests",
        "Run `cargo test` for tests that cover the symbols in `changed_paths` \
         (or, if omitted, the files changed in the working tree). Closes the \
         loop opened by `tokensave_test_map` / `tokensave_test_risk` — emits \
         pass/fail per test alongside the source nodes each test covers. \
         Output is the libtest summary parsed into JSON.",
        json!({
            "type": "object",
            "properties": {
                "changed_paths": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Explicit file paths to compute affected tests from. Defaults to `git diff --name-only` against the working tree."
                },
                "profile": {
                    "type": "string",
                    "enum": ["debug", "release"],
                    "description": "Cargo profile (default: debug)."
                },
                "timeout_secs": {
                    "type": "number",
                    "description": "Maximum wall time before the cargo subprocess is killed (default: 300)."
                },
                "max_tests": {
                    "type": "number",
                    "description": "Cap on tests dispatched in a single invocation (default: 100)."
                }
            }
        }),
    )
}

fn def_ast_grep_rewrite() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_ast_grep_rewrite".to_string(),
        description: "Perform structural code rewrite using ast-grep. The pattern and rewrite use ast-grep's SGPattern syntax. Fails if ast-grep is not installed.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Absolute or project-relative file path"
                },
                "pattern": {
                    "type": "string",
                    "description": "ast-grep search pattern (SGPattern syntax)"
                },
                "rewrite": {
                    "type": "string",
                    "description": "ast-grep rewrite rule"
                }
            },
            "required": ["path", "pattern", "rewrite"]
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "AST Structural Rewrite"
        })),
        meta: None,
    }
}

fn def_session_start() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_session_start".to_string(),
        description: "Save current health metrics as baseline for later comparison via session_end. Call this before starting work.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {}
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Session Start"
        })),
        meta: None,
    }
}

fn def_session_end() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_session_end".to_string(),
        description: "Re-scan and compare current health against session baseline (saved by session_start). Returns diff showing what improved or degraded.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {}
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Session End"
        })),
        meta: None,
    }
}

fn def_body() -> ToolDefinition {
    def(
        "tokensave_body",
        "Symbol Body",
        "Return the full source body of a symbol by name (function, struct, const, etc.). \
         Collapses search + node lookup + file read into a single call. \
         When the name is ambiguous, returns multiple matches ranked by relevance.",
        json!({
            "type": "object",
            "properties": {
                "symbol": {
                    "type": "string",
                    "description": "Symbol name to look up (e.g. 'resolve_provider_api_key', 'CCH_SEED', 'GraphStats'). Qualified names are also accepted."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of matching bodies to return when the name is ambiguous (default: 3, max: 20)"
                }
            },
            "required": ["symbol"]
        }),
    )
}

fn def_todos() -> ToolDefinition {
    def(
        "tokensave_todos",
        "TODOs and FIXMEs",
        "Find TODO, FIXME, XXX, HACK, WIP, NOTE, and unimplemented markers across the project. \
         Each result includes the marker kind, file, line, the comment text, and the enclosing \
         symbol name (function/method) for quick orientation.",
        json!({
            "type": "object",
            "properties": {
                "kinds": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Marker kinds to include (default: TODO, FIXME, XXX, HACK, WIP, NOTE, UNIMPLEMENTED). Matched case-insensitively."
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory path (relative to project root)"
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of markers to return (default: 200, max: 2000)"
                }
            }
        }),
    )
}

fn def_record_decision() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_record_decision".to_string(),
        description: "Persist a design or architecture decision so it can be recalled in a future session via tokensave_session_recall. Use for choices the agent or user would otherwise have to re-explain (e.g. \"use JWT for auth — session tokens flagged by legal\"). Stored in the per-project DB.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "text": {
                    "type": "string",
                    "description": "The decision itself, in one sentence (e.g. \"use JWT for auth\")."
                },
                "reason": {
                    "type": "string",
                    "description": "Optional reason / context (e.g. \"session tokens flagged by legal\")."
                },
                "files": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "File paths that the decision applies to."
                },
                "tags": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Free-form tags for grouping (e.g. \"security\", \"performance\")."
                }
            },
            "required": ["text"]
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Record Decision"
        })),
        meta: None,
    }
}

fn def_record_code_area() -> ToolDefinition {
    ToolDefinition {
        name: "tokensave_record_code_area".to_string(),
        description: "Record that the agent has been working in a code area (a file or directory). The first call sets an optional description; subsequent calls bump the touch counter and update last_touched_at. Recall with tokensave_session_recall.".to_string(),
        input_schema: json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "File or directory path (project-relative)."
                },
                "description": {
                    "type": "string",
                    "description": "Optional short description of what this area is or what was changed."
                }
            },
            "required": ["path"]
        }),
        annotations: Some(json!({
            "readOnlyHint": false,
            "title": "Record Code Area"
        })),
        meta: None,
    }
}

fn def_session_recall() -> ToolDefinition {
    def(
        "tokensave_session_recall",
        "Session Recall",
        "Recall persisted decisions (and optionally code areas) from past sessions. When `query` is provided, runs FTS5 search across decision text and reason. When omitted, returns the most recent decisions newest-first. Pair with tokensave_record_decision.",
        json!({
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "FTS5 query string (e.g. \"auth OR session\"). Omit for newest-first listing."
                },
                "since": {
                    "type": "number",
                    "description": "Unix timestamp; only return decisions made at-or-after this time."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum decisions to return (default: 20, max: 200)."
                },
                "include_code_areas": {
                    "type": "boolean",
                    "description": "If true, also return the top-touched code areas (default: false)."
                }
            }
        }),
    )
}

fn def_field_sites() -> ToolDefinition {
    def(
        "tokensave_field_sites",
        "Field Read/Write Sites",
        "Find every read and write site of a named field across the codebase. \
         Returns two arrays: write_sites (assignments to the field) and \
         read_sites (everything else). Each entry includes file, line, \
         enclosing symbol, and a source snippet. Useful when renaming, \
         removing, or adding an invariant to a field — the write-site list \
         is the exact blast radius. Pattern matches `.<field>` references; \
         field-by-name is shorthand for any struct's same-named field, while \
         `Struct::field` form narrows to a specific declaration.",
        json!({
            "type": "object",
            "properties": {
                "field": {
                    "type": "string",
                    "description": "Field name. Bare name ('last_sync_at') matches across structs; qualified form ('GraphStats::last_sync_at') narrows to one struct's field."
                },
                "writes_only": {
                    "type": "boolean",
                    "description": "When true, returns only write_sites and omits reads. Default false."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum sites per kind (default: 200, max: 2000)."
                }
            },
            "required": ["field"]
        }),
    )
}

fn def_constructors() -> ToolDefinition {
    def(
        "tokensave_constructors",
        "Struct Literal Sites",
        "Find every place a given struct is instantiated as a literal \
         ({ field: value, ... }). Each result includes the file, line, the \
         field list present in that literal, and the set of fields missing \
         relative to the struct's current definition (from the graph). The \
         missing-fields list is the typical refactor signal: after adding a \
         required field, this tool surfaces every site that needs updating, \
         before cargo even compiles. Currently best-effort for Rust source; \
         pattern matching ignores `match` arms and `if let` patterns.",
        json!({
            "type": "object",
            "properties": {
                "struct": {
                    "type": "string",
                    "description": "Struct name to search literal sites of (e.g. 'GraphStats', 'Config')."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of literal sites to return (default: 100, max: 1000)."
                }
            },
            "required": ["struct"]
        }),
    )
}

fn def_signature_search() -> ToolDefinition {
    def(
        "tokensave_signature_search",
        "Signature Search",
        "Find functions and methods by signature shape: return type, parameter \
         substring, async, or path. Searches the cached `signature` column on \
         every Function/Method node. Substring-matched with case-sensitive \
         compare; combine multiple criteria for narrower hits. Use \
         tokensave_search for plain name lookups; this tool is for refactor \
         questions like 'find every function returning Result<_, MyError>' or \
         'every async fn taking &mut self'.",
        json!({
            "type": "object",
            "properties": {
                "returns": {
                    "type": "string",
                    "description": "Substring that must appear in the return-type portion of the signature (after '->'). E.g. 'Result<', 'impl Future', 'Vec<u32>'."
                },
                "params": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Substrings that must all appear in the parameter list portion of the signature. E.g. ['&mut self'], ['i32', 'String']."
                },
                "async": {
                    "type": "boolean",
                    "description": "When true, only return functions marked async. When false, exclude them. Omit to ignore async-ness."
                },
                "path": {
                    "type": "string",
                    "description": "Filter to symbols defined under this directory."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum matches to return (default: 50, max: 500)."
                }
            }
        }),
    )
}

fn def_config() -> ToolDefinition {
    def(
        "tokensave_config",
        "Config File Query",
        "Query TOML or JSON config files by dotted key path. Use 'path' for a \
         single file (e.g. Cargo.toml, tsconfig.json, pyproject.toml) or 'glob' \
         to query the same key across multiple files. The 'key' is dot-separated \
         (e.g. 'package.version', 'dependencies.tokio'). Returns each match's \
         file, parsed value, and the line where the key is defined. Format is \
         detected from extension: .toml → TOML, .json → JSON. \
         \n\nDoes not query the code graph — pure filesystem + parser. Works \
         on uninitialized projects.",
        json!({
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Project-relative path to a single config file (e.g. 'Cargo.toml'). Mutually exclusive with 'glob'."
                },
                "glob": {
                    "type": "string",
                    "description": "Glob pattern to match multiple config files (e.g. '**/Cargo.toml', 'crates/*/Cargo.toml'). Mutually exclusive with 'path'."
                },
                "key": {
                    "type": "string",
                    "description": "Dot-separated key path (e.g. 'package.version', 'dependencies.tokio.version'). Required."
                }
            },
            "required": ["key"]
        }),
    )
}

fn def_diagnostics() -> ToolDefinition {
    def(
        "tokensave_diagnostics",
        "Compile / Type-Check Diagnostics",
        "Run the project's type-checker (cargo check for Rust, tsc for \
         TypeScript, pyright for Python) and return structured errors and \
         warnings. Each diagnostic includes file, line range, level, code, \
         message, driver, and the enclosing graph node when one can be \
         resolved. Replaces the recurring 'run cargo → parse text → read \
         file' loop with a single structured response. \
         \n\nNote: the cargo target dir is forced to .tokensave/target/ so \
         we don't race with the user's interactive cargo runs. The first \
         call against a fresh tree builds dependencies from scratch, which \
         can take several minutes on large workspaces; subsequent calls \
         are sub-second. Build scripts and proc macros from the project \
         execute as part of cargo check — same trust model as running it \
         manually.",
        json!({
            "type": "object",
            "properties": {
                "scope": {
                    "type": "string",
                    "enum": ["workspace", "package", "file"],
                    "description": "Run scope. Default 'workspace'. 'package' requires `name`; 'file' requires `path` and currently runs workspace + post-filter (cargo has no native single-file mode)."
                },
                "name": {
                    "type": "string",
                    "description": "Package name when scope='package' (e.g. 'tokensave', 'serde-json')."
                },
                "path": {
                    "type": "string",
                    "description": "Project-relative file path when scope='file'."
                }
            }
        }),
    )
}

fn def_unsafe_patterns() -> ToolDefinition {
    def(
        "tokensave_unsafe_patterns",
        "Risky Pattern Finder",
        "Find unwrap(), expect(), panic!(), todo!(), unimplemented!(), and unsafe \
         { } sites across the project. Each match includes the file, line, kind, \
         enclosing symbol, the source line, and an in_test flag derived from the \
         path. Use this in security/quality reviews to surface panic sites before \
         a release. Defaults to all kinds; pass `kinds` to narrow.",
        json!({
            "type": "object",
            "properties": {
                "kinds": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Subset of patterns to search. Default: ['unwrap', 'expect', 'panic', 'todo', 'unimplemented', 'unsafe_block']."
                },
                "path": {
                    "type": "string",
                    "description": "Filter to files under this directory (relative to project root)."
                },
                "exclude_tests": {
                    "type": "boolean",
                    "description": "When true, skips files whose path looks like a test (default: false)."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of matches to return (default: 200, max: 2000)."
                }
            }
        }),
    )
}

fn def_implementations() -> ToolDefinition {
    def(
        "tokensave_implementations",
        "Trait / Method Implementations",
        "Find every type implementing a given trait, or every body of a given \
         method name. The 'trait' form returns each implementing type plus the \
         methods on its impl block. The 'method' form returns every function/ \
         method named X across the project, grouped by enclosing type when \
         present. Each result includes file, signature, and the method body.",
        json!({
            "type": "object",
            "properties": {
                "trait": {
                    "type": "string",
                    "description": "Trait name to look up implementations of (e.g. 'LanguageExtractor', 'Display'). Mutually exclusive with 'method'."
                },
                "method": {
                    "type": "string",
                    "description": "Method or function name to find every implementation of (e.g. 'extensions', 'count_complexity'). Mutually exclusive with 'trait'."
                },
                "limit": {
                    "type": "number",
                    "description": "Maximum number of implementations to return (default: 20, max: 200)"
                }
            }
        }),
    )
}

fn def_outline() -> ToolDefinition {
    def(
        "tokensave_outline",
        "File Outline",
        "Flat list of every top-level symbol defined in a file (functions, structs, \
         enums, traits, classes, impls, etc.) — like a table of contents. Sorted by \
         line number; no code bodies. Optional 'kinds' filter narrows to specific \
         node kinds. Use this as the cheapest way to orient before zooming into a \
         large file with tokensave_node, tokensave_body, or tokensave_read.",
        json!({
            "type": "object",
            "properties": {
                "file": {
                    "type": "string",
                    "description": "Project-relative path to the file (e.g. 'src/sync.rs')."
                },
                "kinds": {
                    "type": "array",
                    "items": { "type": "string" },
                    "description": "Optional filter on node kinds. Common values: 'function', 'struct', 'enum', 'trait', 'impl', 'class', 'method', 'const'. Case-insensitive. Default: all kinds."
                }
            },
            "required": ["file"]
        }),
    )
}

fn def_read() -> ToolDefinition {
    def(
        "tokensave_read",
        "Read File (mode-aware)",
        "Read a file or its symbol map. Modes: 'full' (entire file), 'lines' \
         (1-based inclusive byte-range slice via the 'lines' arg, e.g. '120-180'), \
         'map' (flat list of every top-level symbol from the graph — no source \
         bytes touched), 'signatures' (functions and types with their cached \
         signature). Cross-session cached: a re-call on an unchanged file returns \
         a tiny stub with 'unchanged: true'.",
        json!({
            "type": "object",
            "properties": {
                "file": {
                    "type": "string",
                    "description": "Project-relative or absolute path to the file (e.g. 'src/sync.rs')."
                },
                "mode": {
                    "type": "string",
                    "enum": ["full", "lines", "map", "signatures"],
                    "description": "Read mode. Default: 'full'."
                },
                "lines": {
                    "type": "string",
                    "description": "Required when mode='lines'. Format 'A-B' or single 'A' (1-based, inclusive). E.g. '120-180' or '42'."
                }
            },
            "required": ["file"]
        }),
    )
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::unreadable_literal)]
mod tests {
    use super::*;

    #[test]
    fn test_explore_call_budget_tiers() {
        assert_eq!(explore_call_budget(0), 3);
        assert_eq!(explore_call_budget(5000), 3);
        assert_eq!(explore_call_budget(5001), 4);
        assert_eq!(explore_call_budget(20000), 4);
        assert_eq!(explore_call_budget(20001), 5);
        assert_eq!(explore_call_budget(80000), 5);
        assert_eq!(explore_call_budget(80001), 7);
        assert_eq!(explore_call_budget(250000), 7);
        assert_eq!(explore_call_budget(250001), 10);
    }

    #[test]
    fn test_context_description_contains_budget() {
        let desc = context_description(5000, 4);
        assert!(
            desc.contains("4 calls maximum"),
            "description should contain budget: {desc}"
        );
        assert!(
            desc.contains("5000 nodes"),
            "description should contain node count: {desc}"
        );
    }

    #[test]
    fn test_get_tool_definitions_with_budget() {
        let defs = get_tool_definitions_with_budget(10000, 4);
        let context_tool = defs.iter().find(|d| d.name == "tokensave_context").unwrap();
        assert!(context_tool.description.contains("4 calls maximum"));
        assert!(context_tool.description.contains("10000 nodes"));
    }
}
