//! Claude Code hook adapter (PreToolUse).
//!
//! Wire format: stdin is one JSON object with `tool_name` and `tool_input`.
//! Stdout is `{"hookSpecificOutput": {...}}` with exit code 0; the JSON
//! carries the verdict.

use super::{AdapterError, HarnessAdapter, PathKind, ToolCall, ToolOp};
use crate::core::Decision;
use serde_json::{json, Value};
use std::path::PathBuf;

pub struct ClaudeAdapter;

impl HarnessAdapter for ClaudeAdapter {
    fn name(&self) -> &'static str {
        "claude"
    }

    fn parse_request(&self, input: &[u8]) -> Result<ToolCall, AdapterError> {
        let v: Value = serde_json::from_slice(input)?;
        let tool_name = v
            .get("tool_name")
            .and_then(|x| x.as_str())
            .ok_or_else(|| AdapterError::Parse("missing tool_name".into()))?
            .to_string();
        let tool_input = v.get("tool_input").cloned().unwrap_or(Value::Null);

        let op = match tool_name.as_str() {
            "Read" => path_op(&tool_input, PathKind::Read)?,
            "Write" | "Edit" | "MultiEdit" => path_op(&tool_input, PathKind::Write)?,
            "Bash" => command_op(&tool_input)?,
            other => return Err(AdapterError::UnsupportedTool(other.to_string())),
        };

        Ok(ToolCall {
            tool_name,
            op,
            raw: v,
        })
    }

    fn render_decision(&self, _call: &ToolCall, decision: &Decision) -> Result<Vec<u8>, AdapterError> {
        let (verdict, reason) = match decision {
            Decision::Allow => ("allow", String::new()),
            Decision::Ask(r) => ("ask", r.message.clone()),
            Decision::Deny(r) => ("deny", r.message.clone()),
        };
        let out = json!({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": verdict,
                "permissionDecisionReason": reason,
            }
        });
        Ok(serde_json::to_vec(&out)?)
    }
}

fn path_op(tool_input: &Value, kind: PathKind) -> Result<ToolOp, AdapterError> {
    let p = tool_input
        .get("file_path")
        .and_then(|x| x.as_str())
        .ok_or_else(|| AdapterError::Parse("missing tool_input.file_path".into()))?;
    Ok(ToolOp::Path {
        path: PathBuf::from(p),
        kind,
    })
}

fn command_op(tool_input: &Value) -> Result<ToolOp, AdapterError> {
    let c = tool_input
        .get("command")
        .and_then(|x| x.as_str())
        .ok_or_else(|| AdapterError::Parse("missing tool_input.command".into()))?;
    Ok(ToolOp::Command { text: c.to_string() })
}
