//! Harness adapter layer. Normalizes harness-specific payloads into
//! `core` types and renders `Decision` back to harness wire format.

use crate::core::Decision;
use std::path::PathBuf;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AdapterError {
    #[error("invalid request payload: {0}")]
    Parse(String),
    #[error("unsupported tool: {0}")]
    UnsupportedTool(String),
    #[error("io: {0}")]
    Io(#[from] std::io::Error),
    #[error("json: {0}")]
    Json(#[from] serde_json::Error),
}

/// Normalized tool-call shape consumed by `core::Policy`.
/// Adapters translate harness-specific payloads into this; nothing in
/// `core` knows about adapters.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolCall {
    /// Harness's tool name (e.g. "Read", "Write", "Edit", "Bash").
    pub tool_name: String,
    /// Op classification derived from `tool_name`.
    pub op: ToolOp,
    /// Original raw payload for the adapter to consult when rendering.
    pub raw: serde_json::Value,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToolOp {
    Path { path: PathBuf, kind: PathKind },
    Command { text: String },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathKind {
    Read,
    Write,
}

/// Trait implemented by each harness adapter. Adapters parse the harness's
/// hook stdin payload into `ToolCall` and render a `Decision` back to the
/// harness's expected stdout format.
pub trait HarnessAdapter {
    /// The CLI name (e.g. "claude", "codex", "gemini").
    fn name(&self) -> &'static str;

    fn parse_request(&self, input: &[u8]) -> Result<ToolCall, AdapterError>;

    fn render_decision(&self, call: &ToolCall, decision: &Decision) -> Result<Vec<u8>, AdapterError>;
}

#[cfg(feature = "harness-claude")]
pub mod claude;

/// Look up a registered adapter by CLI name.
pub fn lookup(name: &str) -> Option<Box<dyn HarnessAdapter>> {
    match name {
        #[cfg(feature = "harness-claude")]
        "claude" => Some(Box::new(claude::ClaudeAdapter)),
        _ => None,
    }
}
