use async_trait::async_trait;

use super::workspace_op_error;
use crate::tools::builtin_tool::{BuiltinTool, BuiltinToolContext, BuiltinToolError};
use crate::tools::capability::Capabilities;
use steer_tools::result::AstGrepResult;
use steer_tools::tools::astgrep::{AstGrepError, AstGrepParams, AstGrepToolSpec};
use steer_workspace::{AstGrepRequest, WorkspaceOpContext};

pub struct AstGrepTool;

#[async_trait]
impl BuiltinTool for AstGrepTool {
    type Params = AstGrepParams;
    type Output = AstGrepResult;
    type Spec = AstGrepToolSpec;

    const DESCRIPTION: &'static str = r#"Structural code search using abstract syntax trees (AST).
- Searches code by its syntactic structure, not just text patterns
- Use $METAVAR placeholders (e.g., $VAR, $FUNC, $ARGS) to match any code element
- Supports all major languages: rust, javascript, typescript, python, java, go, etc.
Pattern examples:
- "console.log($MSG)" - finds all console.log calls regardless of argument
- "fn $NAME($PARAMS) { $BODY }" - finds all Rust function definitions
- "if $COND { $THEN } else { $ELSE }" - finds all if-else statements
- "import $WHAT from '$MODULE'" - finds all ES6 imports from specific modules
- "$VAR = $VAR + $EXPR" - finds all self-incrementing assignments
Advanced patterns:
- "function $FUNC($$$ARGS) { $$$ }" - $$$ matches any number of elements
- "foo($ARG, ...)" - ellipsis matches remaining arguments
- Use any valid code as a pattern - ast-grep understands the syntax!
Automatically respects .gitignore files"#;
    const REQUIRES_APPROVAL: bool = false;
    const REQUIRED_CAPABILITIES: Capabilities = Capabilities::WORKSPACE;

    async fn execute(
        &self,
        params: Self::Params,
        ctx: &BuiltinToolContext,
    ) -> Result<Self::Output, BuiltinToolError<AstGrepError>> {
        let request = AstGrepRequest {
            pattern: params.pattern,
            lang: params.lang,
            include: params.include,
            exclude: params.exclude,
            path: params.path,
        };
        let op_ctx =
            WorkspaceOpContext::new(ctx.tool_call_id.0.clone(), ctx.cancellation_token.clone());
        let result = ctx
            .services
            .workspace
            .astgrep(request, &op_ctx)
            .await
            .map_err(|e| {
                BuiltinToolError::execution(AstGrepError::Workspace(workspace_op_error(e)))
            })?;
        Ok(AstGrepResult(result))
    }
}
