use serde::{Deserialize, Serialize};
use serde_json::{json, Value};

pub const PROTOCOL_VERSION: &str = "1.0";

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExecutionSurfaceKind {
    PrivilegedBrowserPipe,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct BrowserContext {
    pub page_url: String,
    pub page_title: String,
}

impl BrowserContext {
    pub fn is_empty(&self) -> bool {
        self.page_url.trim().is_empty() && self.page_title.trim().is_empty()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutionSurfaceMetadata {
    pub kind: ExecutionSurfaceKind,
    pub privileged: bool,
    pub defines_runtime_identity: bool,
    pub guard: String,
    #[serde(default)]
    pub allowed_domains: Vec<String>,
    #[serde(default)]
    pub allowed_actions: Vec<String>,
}

impl ExecutionSurfaceMetadata {
    pub fn privileged_browser_pipe(guard: impl Into<String>) -> Self {
        Self {
            kind: ExecutionSurfaceKind::PrivilegedBrowserPipe,
            privileged: true,
            defines_runtime_identity: false,
            guard: guard.into(),
            allowed_domains: Vec::new(),
            allowed_actions: Vec::new(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BrowserMessage {
    Init {
        version: String,
        hmac_seed: String,
        #[serde(default)]
        capabilities: Vec<String>,
    },
    Connect,
    Start,
    Stop,
    SubmitTask {
        instruction: String,
        #[serde(default)]
        conversation_id: String,
        #[serde(default)]
        messages: Vec<ConversationMessage>,
        #[serde(default)]
        page_url: String,
        #[serde(default)]
        page_title: String,
    },
    Response {
        seq: u64,
        success: bool,
        #[serde(default = "default_object")]
        data: Value,
        #[serde(default)]
        aom_snapshot: Vec<Value>,
        timing: Timing,
    },
}

impl BrowserMessage {
    pub fn browser_context(&self) -> Option<BrowserContext> {
        match self {
            Self::SubmitTask {
                page_url,
                page_title,
                ..
            } => {
                let context = BrowserContext {
                    page_url: page_url.clone(),
                    page_title: page_title.clone(),
                };
                (!context.is_empty()).then_some(context)
            }
            _ => None,
        }
    }

    pub fn requested_surface_metadata(&self) -> Option<ExecutionSurfaceMetadata> {
        match self {
            Self::SubmitTask { .. } => Some(ExecutionSurfaceMetadata::privileged_browser_pipe(
                "browser_host_and_mac_policy",
            )),
            _ => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConversationMessage {
    pub role: String,
    pub content: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentMessage {
    InitAck {
        version: String,
        agent_id: String,
        supported_actions: Vec<Action>,
    },
    StatusChanged {
        state: String,
    },
    LogEntry {
        level: String,
        message: String,
    },
    TaskComplete {
        success: bool,
        summary: String,
    },
    Command {
        seq: u64,
        action: Action,
        params: Value,
        security: SecurityFields,
    },
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Action {
    Click,
    Type,
    Navigate,
    GetText,
    Eval,
    GetHtml,
    WaitForSelector,
    PageScreenshot,
    Select,
    ScrollTo,
    GetAomSnapshot,
    StorageSet,
    StorageGet,
    ZombieSpawn,
    ZombieKill,
}

impl Action {
    pub fn as_str(&self) -> &'static str {
        match self {
            Action::Click => "click",
            Action::Type => "type",
            Action::Navigate => "navigate",
            Action::GetText => "getText",
            Action::Eval => "eval",
            Action::GetHtml => "getHtml",
            Action::WaitForSelector => "waitForSelector",
            Action::PageScreenshot => "pageScreenshot",
            Action::Select => "select",
            Action::ScrollTo => "scrollTo",
            Action::GetAomSnapshot => "getAomSnapshot",
            Action::StorageSet => "storageSet",
            Action::StorageGet => "storageGet",
            Action::ZombieSpawn => "zombieSpawn",
            Action::ZombieKill => "zombieKill",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecurityFields {
    pub expected_domain: String,
    pub hmac: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Timing {
    pub queue_ms: u64,
    pub exec_ms: u64,
}

pub fn supported_actions() -> Vec<Action> {
    vec![
        Action::Click,
        Action::Type,
        Action::Navigate,
        Action::GetText,
        Action::Eval,
        Action::GetHtml,
        Action::WaitForSelector,
        Action::PageScreenshot,
        Action::Select,
        Action::ScrollTo,
        Action::GetAomSnapshot,
        Action::StorageSet,
        Action::StorageGet,
        Action::ZombieSpawn,
        Action::ZombieKill,
    ]
}

fn default_object() -> Value {
    json!({})
}
