pub mod artifact;
pub mod manifest;
pub mod page;

use serde::Deserialize;
use serde_json::Value;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolCallInput {
    pub tool_name: String,
    pub workspace_id: Option<String>,
    pub document_id: Option<String>,
    pub actor_id: Option<String>,
    pub session_id: Option<String>,
    pub run_id: Option<String>,
    pub tool_call_id: Option<String>,
    pub trace_id: Option<String>,
    pub idempotency_key: Option<String>,
    pub dry_run: Option<bool>,
    pub capability_scope: Option<Vec<String>>,
    pub args: Option<Value>,
}

impl ToolCallInput {
    pub fn arg_string(&self, key: &str) -> Option<String> {
        self.args
            .as_ref()
            .and_then(|args| args.get(key))
            .and_then(Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(ToOwned::to_owned)
    }

    pub fn arg_value(&self, key: &str) -> Option<Value> {
        self.args.as_ref().and_then(|args| args.get(key)).cloned()
    }

    pub fn effective_workspace_id(&self) -> Option<String> {
        self.workspace_id
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(ToOwned::to_owned)
            .or_else(|| self.arg_string("workspaceId"))
    }

    pub fn effective_document_id(&self) -> Option<String> {
        self.document_id
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(ToOwned::to_owned)
            .or_else(|| self.arg_string("documentId"))
    }

    pub fn effective_tool_call_id(&self) -> String {
        self.tool_call_id
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .unwrap_or("tool_call_missing")
            .to_string()
    }

    pub fn effective_trace_id<'a>(&'a self, fallback: &'a str) -> &'a str {
        self.trace_id
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .unwrap_or(fallback)
    }

    pub fn idempotency_key_or_default(&self, fallback: &str) -> String {
        self.idempotency_key
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .unwrap_or(fallback)
            .to_string()
    }

    pub fn has_idempotency_key(&self) -> bool {
        self.idempotency_key
            .as_deref()
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .is_some()
    }
}
