//! MCP approval → ACP `session/request_permission`.
//!
//! When the stream yields an `mcp_approval_request`, `Conversation::apply`
//! records a `PendingApproval`. This module asks the client to decide via an ACP
//! permission request, maps the chosen option back to an approve/deny boolean
//! (remembering `*_always` answers per server), and the turn loop drains the
//! decisions into the next request as `mcp_approval_response` items.

use std::collections::HashMap;

use agent_client_protocol as acp;
use agent_client_protocol::ConnectionTo;
use agent_client_protocol::schema::{
    PermissionOption, PermissionOptionKind, RequestPermissionOutcome, RequestPermissionRequest,
    SessionId, ToolCallId, ToolCallStatus, ToolCallUpdate, ToolCallUpdateFields, ToolKind,
};
use hagent_core::PendingApproval;

/// Ask the client to approve/deny one pending MCP tool call. Returns the
/// decision; records `allow_always`/`reject_always` in `remembered` (keyed by
/// server label) so future calls to that server resolve without a round-trip.
pub async fn request_decision(
    cx: &ConnectionTo<acp::Client>,
    session_id: &SessionId,
    approval: &PendingApproval,
    remembered: &mut HashMap<String, bool>,
) -> bool {
    let tool_call = ToolCallUpdate::new(
        ToolCallId::new(approval.request_id.clone()),
        ToolCallUpdateFields::new()
            .title(format!("{}::{}", approval.server_label, approval.name))
            .kind(ToolKind::Other)
            .status(ToolCallStatus::Pending)
            .raw_input(parse_json_or_string(&approval.arguments)),
    );
    let options = vec![
        PermissionOption::new("allow_once", "Allow once", PermissionOptionKind::AllowOnce),
        PermissionOption::new(
            "allow_always",
            "Always allow this server",
            PermissionOptionKind::AllowAlways,
        ),
        PermissionOption::new("reject_once", "Reject", PermissionOptionKind::RejectOnce),
        PermissionOption::new(
            "reject_always",
            "Always reject this server",
            PermissionOptionKind::RejectAlways,
        ),
    ];
    let request = RequestPermissionRequest::new(session_id.clone(), tool_call, options);

    // Safe to block here: we're inside the spawned turn task, not the dispatch
    // loop, so awaiting the client's response doesn't deadlock.
    match cx.send_request(request).block_task().await {
        Ok(resp) => interpret(resp.outcome, &approval.server_label, remembered),
        Err(e) => {
            tracing::warn!("request_permission failed ({e}); treating as reject");
            false
        }
    }
}

fn interpret(
    outcome: RequestPermissionOutcome,
    label: &str,
    remembered: &mut HashMap<String, bool>,
) -> bool {
    match outcome {
        RequestPermissionOutcome::Selected(sel) => match sel.option_id.to_string().as_str() {
            "allow_once" => true,
            "allow_always" => {
                remembered.insert(label.to_string(), true);
                true
            }
            "reject_always" => {
                remembered.insert(label.to_string(), false);
                false
            }
            // "reject_once" or any unknown option → reject this time.
            _ => false,
        },
        // Cancelled (turn cancelled before the user answered) → reject.
        _ => false,
    }
}

fn parse_json_or_string(s: &str) -> serde_json::Value {
    serde_json::from_str(s).unwrap_or_else(|_| serde_json::Value::String(s.to_string()))
}
