//! Event Translation Layer
//!
//! This module provides translation from Dirigent protocol Events to ACP notifications.
//! It handles the mapping between internal event representations and the wire format
//! expected by ACP clients.

use dirigent_protocol::{Event, SessionUpdate};

use super::content_transform::{
    extract_kind, rebuild_meta, tool_call_status_to_string, unwrap_tool_call_content,
};
use super::types::{
    models_to_config_option, modes_to_config_option, ConfigOption, ConfigOptionChoice,
    ConfigOptionType, SessionUpdateParams, SessionUpdateVariant,
};

/// Translate a Dirigent protocol Event to ACP notifications
///
/// Not all Dirigent events need to be forwarded to ACP clients. This function
/// returns a vector of notifications for events that should be sent, and an empty
/// vector for events that should be filtered out.
///
/// ## Mapped Events
///
/// - `Event::SessionUpdate` with `SessionUpdate::AgentMessageChunk` -> `MessageChunk`
/// - `Event::SessionUpdate` with `SessionUpdate::AgentThoughtChunk` -> `MessageChunk` (with content_type)
/// - `Event::SessionUpdate` with `SessionUpdate::ToolCall` -> `ToolCallUpdate`
/// - `Event::SessionUpdate` with `SessionUpdate::ToolCallUpdate` -> `ToolCallUpdate`
/// - `Event::MessageCompleted` -> `MessageComplete`
/// - `Event::SessionIdle` -> `SessionIdle`
/// - `Event::SessionMetadataReceived` -> `ConfigOptionUpdate` (with modes and models)
/// - `Event::MessageFailed` -> `SessionError`
/// - `Event::Error` -> `SessionError` (system-wide error)
///
/// ## Filtered Events
///
/// - `Event::SessionsListed` - List operations don't need streaming
/// - `Event::SessionCreated` - Handled via RPC response
/// - `Event::SessionUpdated` - Metadata updates, not content
/// - `Event::SessionMetadataUpdated` - Metadata updates
/// - `Event::SessionDeleted` - Handled via RPC
/// - `Event::Connected` / `Event::Disconnected` - System events
/// - `Event::ConnectorCreated` / `Event::ConnectorRemoved` - System events
/// - `Event::MessagesListed` - List operations
/// - `Event::MessageStarted` - Initial message, content comes via chunks
///
/// # Parameters
///
/// - `event`: The Dirigent protocol event to translate
///
/// # Returns
///
/// Vec of `SessionUpdateParams` to be sent. Most events return 0 or 1 update.
pub fn translate_event(event: &Event) -> Vec<SessionUpdateParams> {
    match event {
        // SessionUpdate events - the main streaming content
        Event::SessionUpdate {
            session_id, update, ..
        } => translate_session_update(session_id, update)
            .map(|u| vec![u])
            .unwrap_or_default(),

        // Message completion
        Event::MessageCompleted { message, .. } => vec![SessionUpdateParams {
            session_id: message.session_id.clone(),
            update: SessionUpdateVariant::MessageComplete {
                message_id: Some(message.id.clone()),
            },
            ..Default::default()
        }],

        // Session idle
        Event::SessionIdle { session_id, .. } => vec![SessionUpdateParams {
            session_id: session_id.clone(),
            update: SessionUpdateVariant::SessionIdle {},
            ..Default::default()
        }],

        // Session metadata received - forward as BOTH config_option_update AND current_mode_update
        //
        // We emit both notification types to support:
        // - New clients (acp-beta flag): Use config_option_update
        // - Legacy clients: Use current_mode_update (mode only - no legacy model updates exist)
        //
        // Clients safely ignore notification types they don't understand.
        // See: https://agentclientprotocol.com/rfds/session-config-options
        Event::SessionMetadataReceived {
            session_id,
            models,
            modes,
            config_options: event_config_options,
            ..
        } => {
            let mut updates = vec![];

            // If the event already has config_options from the agent, prefer those
            // over converting from modes/models (agent-provided options are authoritative).
            if let Some(agent_config_options) = event_config_options {
                if !agent_config_options.is_empty() {
                    // Emit ConfigOptionUpdate with agent-provided options
                    updates.push(SessionUpdateParams {
                        session_id: session_id.clone(),
                        update: SessionUpdateVariant::ConfigOptionUpdate {
                            config_options: agent_config_options.iter().map(|co| ConfigOption {
                                id: co.id.clone(),
                                name: co.name.clone(),
                                description: co.description.clone(),
                                category: co.category.clone(),
                                option_type: match co.option_type {
                                    dirigent_protocol::ConfigOptionType::Select => ConfigOptionType::Select,
                                },
                                current_value: co.current_value.clone(),
                                options: Some(co.options.iter().map(|v| ConfigOptionChoice {
                                    value: v.value.clone(),
                                    name: v.name.clone(),
                                    description: v.description.clone(),
                                }).collect()),
                            }).collect(),
                        },
                        ..Default::default()
                    });

                    // Also emit CurrentModeUpdate for legacy clients if a mode option exists
                    if let Some(mode_opt) = agent_config_options.iter().find(|co| co.id == "mode" || co.category.as_deref() == Some("mode")) {
                        updates.push(SessionUpdateParams {
                            session_id: session_id.clone(),
                            update: SessionUpdateVariant::CurrentModeUpdate {
                                mode_id: mode_opt.current_value.clone(),
                            },
                            ..Default::default()
                        });
                    }

                    return updates;
                }
            }

            // Fall back to building config_options from legacy modes/models fields
            let mut config_options: Vec<ConfigOption> = vec![];
            if let Some(modes_state) = modes {
                config_options.push(modes_to_config_option(modes_state));

                // Also emit CurrentModeUpdate for legacy clients
                // Legacy protocol only supports mode updates (no model updates)
                updates.push(SessionUpdateParams {
                    session_id: session_id.clone(),
                    update: SessionUpdateVariant::CurrentModeUpdate {
                        mode_id: modes_state.current_mode_id.clone(),
                    },
                    ..Default::default()
                });
            }

            if let Some(models_state) = models {
                config_options.push(models_to_config_option(models_state));
                // Note: Legacy protocol has no CurrentModelUpdate - only new ConfigOptionUpdate
            }

            if !config_options.is_empty() {
                updates.push(SessionUpdateParams {
                    session_id: session_id.clone(),
                    update: SessionUpdateVariant::ConfigOptionUpdate { config_options },
                    ..Default::default()
                });
            }

            updates
        }

        // Message failure - not forwarded in new structure (no equivalent variant)
        Event::MessageFailed { .. } => vec![],

        // System-wide error - not forwarded in new structure (no equivalent variant)
        Event::Error { .. } => vec![],

        // Agent request - will be handled in Phase 2 (bidirectional request/response)
        // For now, not forwarded (requires new SSE variant and response mechanism)
        Event::AgentRequest { .. } => vec![],

        // Events that should not be forwarded via this translation path
        // (they may be handled elsewhere or are not relevant to ACP clients)
        Event::SessionsListed { .. }
        | Event::SessionCreated { .. }
        | Event::SessionUpdated { .. }
        | Event::SessionMetadataUpdated { .. }
        | Event::SessionDeleted { .. }
        | Event::SessionClosed { .. }
        | Event::SessionSystemMessageSet { .. }
        | Event::SessionError { .. }
        | Event::SessionTransferred { .. }
        | Event::ForwardingPanic { .. }
        | Event::MessagesListed { .. }
        | Event::MessageStarted { .. }
        | Event::TurnComplete { .. }
        | Event::ConnectorCreated { .. }
        | Event::ConnectorRemoved { .. }
        | Event::ConnectorStateChanged { .. }
        | Event::Connected
        | Event::Disconnected => vec![],

        // ACP client connection events - these are handled by the UI directly
        // via the main SSE endpoint, not translated to session updates
        Event::AcpClientConnected { .. }
        | Event::AcpClientDisconnected { .. }
        | Event::AcpClientSessionOpened { .. }
        | Event::AcpClientSessionRouted { .. } => vec![],

        // Inspector events - not relevant for ACP session updates
        Event::InspectorSnapshot { .. }
        | Event::InspectorNodeRegistered { .. }
        | Event::InspectorNodeRemoved { .. }
        | Event::InspectorStateChanged { .. }
        | Event::InspectorPropertiesUpdated { .. } => vec![],

        // Archivist-to-frontend signal — not relevant for ACP clients
        Event::SessionRegistered { .. } => vec![],

        // System task events — internal UI concern, not relevant for ACP clients
        Event::SystemTaskStatusChanged { .. } => vec![],
    }
}

/// Translate a SessionUpdate to a SessionUpdateParams
fn translate_session_update(
    session_id: &str,
    update: &SessionUpdate,
) -> Option<SessionUpdateParams> {
    match update {
        // Agent message content chunk
        SessionUpdate::AgentMessageChunk { content, .. } => Some(SessionUpdateParams {
            session_id: session_id.to_string(),
            update: SessionUpdateVariant::AgentMessageChunk {
                content: content.clone(),
            },
            ..Default::default()
        }),

        // Agent thought chunk - render as agent message (thoughts shown as agent text in UI)
        SessionUpdate::AgentThoughtChunk { content, .. } => Some(SessionUpdateParams {
            session_id: session_id.to_string(),
            update: SessionUpdateVariant::AgentMessageChunk {
                content: content.clone(),
            },
            ..Default::default()
        }),

        // User message chunks are typically not streamed to other clients
        // Filter them out for now
        SessionUpdate::UserMessageChunk { .. } => None,

        // Tool call initiated - forward to client
        SessionUpdate::ToolCall {
            tool_call, _meta, ..
        } => Some(SessionUpdateParams {
            session_id: session_id.to_string(),
            update: SessionUpdateVariant::ToolCall {
                tool_call_id: tool_call.id.clone(),
                title: tool_call.title.clone(),
                kind: extract_kind(tool_call),
                raw_input: tool_call.raw_input.clone(),
                status: Some(tool_call_status_to_string(&tool_call.status)),
                content: unwrap_tool_call_content(&tool_call.content),
                _meta: rebuild_meta(_meta, tool_call),
            },
            ..Default::default()
        }),

        // Tool call update - forward to client
        SessionUpdate::ToolCallUpdate {
            tool_call, _meta, ..
        } => Some(SessionUpdateParams {
            session_id: session_id.to_string(),
            update: SessionUpdateVariant::ToolCallUpdate {
                tool_call_id: tool_call.id.clone(),
                status: Some(tool_call_status_to_string(&tool_call.status)),
                content: unwrap_tool_call_content(&tool_call.content),
                raw_output: tool_call.raw_output.clone(),
                error: tool_call.error.clone(),
                _meta: rebuild_meta(_meta, tool_call),
            },
            ..Default::default()
        }),

        // Unknown update type - forward as-is for transparent proxying
        SessionUpdate::Unknown { data } => Some(SessionUpdateParams {
            session_id: session_id.to_string(),
            update: SessionUpdateVariant::Unknown { data: data.clone() },
            ..Default::default()
        }),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use dirigent_protocol::{ContentBlock, Message, MessageRole, MessageStatus};

    #[test]
    fn test_translate_session_update_agent_chunk() {
        let event = Event::SessionUpdate {
            connector_id: "conn-1".to_string(),
            session_id: "sess-1".to_string(),
            update: SessionUpdate::AgentMessageChunk {
                message_id: "msg-1".to_string(),
                content: ContentBlock::Text {
                    text: "Hello".to_string(),
                },
                _meta: None,
            },
        };

        let updates = translate_event(&event);
        assert_eq!(updates.len(), 1);

        let update_params = &updates[0];
        assert_eq!(update_params.session_id, "sess-1");

        match &update_params.update {
            SessionUpdateVariant::AgentMessageChunk { content } => match content {
                ContentBlock::Text { text } => {
                    assert_eq!(text, "Hello");
                }
                _ => panic!("Expected Text content"),
            },
            _ => panic!("Expected AgentMessageChunk variant"),
        }
    }

    #[test]
    fn test_translate_session_update_thought_chunk() {
        let event = Event::SessionUpdate {
            connector_id: "conn-1".to_string(),
            session_id: "sess-1".to_string(),
            update: SessionUpdate::AgentThoughtChunk {
                message_id: "msg-1".to_string(),
                content: ContentBlock::Text {
                    text: "Thinking...".to_string(),
                },
                _meta: None,
            },
        };

        let updates = translate_event(&event);
        assert_eq!(updates.len(), 1);

        let update_params = &updates[0];
        assert_eq!(update_params.session_id, "sess-1");

        // Thought chunks are rendered as agent message chunks
        match &update_params.update {
            SessionUpdateVariant::AgentMessageChunk { content } => match content {
                ContentBlock::Text { text } => {
                    assert_eq!(text, "Thinking...");
                }
                _ => panic!("Expected Text content"),
            },
            _ => panic!("Expected AgentMessageChunk variant"),
        }
    }

    #[test]
    fn test_translate_message_completed() {
        let now = chrono::Utc::now();
        let event = Event::MessageCompleted {
            connector_id: "conn-1".to_string(),
            message: Message {
                id: "msg-1".to_string(),
                session_id: "sess-1".to_string(),
                role: MessageRole::Assistant,
                created_at: now,
                content: vec![],
                status: MessageStatus::Completed,
                metadata: None,
            },
        };

        let updates = translate_event(&event);
        assert_eq!(updates.len(), 1);

        let update_params = &updates[0];
        assert_eq!(update_params.session_id, "sess-1");

        match &update_params.update {
            SessionUpdateVariant::MessageComplete { message_id } => {
                assert_eq!(message_id, &Some("msg-1".to_string()));
            }
            _ => panic!("Expected MessageComplete variant"),
        }
    }

    #[test]
    fn test_translate_session_idle() {
        let event = Event::SessionIdle {
            connector_id: "test-connector".to_string(),
            session_id: "sess-1".to_string(),
        };

        let updates = translate_event(&event);
        assert_eq!(updates.len(), 1);

        let update_params = &updates[0];
        assert_eq!(update_params.session_id, "sess-1");

        match &update_params.update {
            SessionUpdateVariant::SessionIdle {} => {
                // Expected
            }
            _ => panic!("Expected SessionIdle variant"),
        }
    }

    #[test]
    fn test_translate_message_failed() {
        let event = Event::MessageFailed {
            message_id: "msg-1".to_string(),
            error: "Connection lost".to_string(),
        };

        // MessageFailed events are now filtered (return empty vec)
        let updates = translate_event(&event);
        assert!(
            updates.is_empty(),
            "MessageFailed events should be filtered"
        );
    }

    #[test]
    fn test_translate_filtered_events() {
        // These events should not produce notifications
        let filtered_events = vec![
            Event::Connected,
            Event::Disconnected,
            Event::SessionDeleted {
                session_id: "s".to_string(),
            },
            Event::Error {
                message: "System error".to_string(),
            },
            Event::MessageFailed {
                message_id: "msg-1".to_string(),
                error: "Failed".to_string(),
            },
        ];

        for event in filtered_events {
            assert!(
                translate_event(&event).is_empty(),
                "Expected {:?} to be filtered",
                event
            );
        }
    }
}
