//! Core types for the Dirigent runtime
//!
//! This module defines the fundamental types used throughout the dirigent_core
//! system for connector management and orchestration.

use serde::{Deserialize, Serialize};

// Re-export user types from dirigent_auth
pub use dirigent_auth::{User, UserId, UserProfile};

/// Unique identifier for a connector instance
///
/// Each connector in the system has a unique ID that is used to reference it
/// across API calls, events, and internal state management.
pub type ConnectorId = String;

/// Type of connector
///
/// Identifies the underlying agent system or protocol that a connector bridges to.
/// Each variant represents a different integration with an external agent provider.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ConnectorKind {
    /// OpenCode.ai connector (REST + SSE)
    OpenCode,
    /// Agent-Client Protocol connector
    Acp,
    /// Mock connector for testing
    Mock,
    /// Acceptor for incoming connections (accepts inbound ACP connections)
    ///
    /// Unlike other ConnectorKind variants which initiate outbound connections,
    /// Acceptor represents an entry point for incoming connections from external
    /// ACP clients. Sessions from Acceptors are routed to other connectors for processing.
    Acceptor,
    /// Gateway connector for handling messages locally
    ///
    /// The Gateway connector handles messages locally with configurable behavior
    /// including echo mode and built-in commands. It serves as the default connector
    /// for incoming ACP sessions before they are routed to an external agent.
    Gateway,
}

impl std::fmt::Display for ConnectorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConnectorKind::OpenCode => write!(f, "OpenCode"),
            ConnectorKind::Acp => write!(f, "ACP"),
            ConnectorKind::Mock => write!(f, "Mock"),
            ConnectorKind::Acceptor => write!(f, "Acceptor"),
            ConnectorKind::Gateway => write!(f, "Gateway"),
        }
    }
}

impl ConnectorKind {
    /// Whether this connector kind can receive session transfers
    ///
    /// Returns true for connector types that can have sessions
    /// transferred to them from Gateway or other sources.
    pub fn supports_session_transfer(&self) -> bool {
        match self {
            ConnectorKind::OpenCode => true,
            ConnectorKind::Acp => true,
            ConnectorKind::Gateway => true,
            ConnectorKind::Mock => false,     // Testing only
            ConnectorKind::Acceptor => false, // Entry point only, doesn't process
        }
    }
}

/// Current state of a connector
///
/// Tracks the lifecycle state of a connector from initialization through
/// active operation to shutdown. State transitions are managed by the
/// connector's task loop and can be observed by clients.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum ConnectorState {
    /// Connector is being initialized but not yet connecting
    Initializing,
    /// Connector is attempting to establish connection
    Connecting,
    /// Connector is connected and operational
    Ready,
    /// Connector encountered an error (contains error message)
    Error(String),
    /// Connector has been stopped (intentionally or after unrecoverable error)
    Stopped,
}

/// Structured reason for connector error state.
///
/// When `ConnectorState` is `Error(String)`, this provides a machine-readable
/// classification of the error for UI display and capability computation.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum ConnectorErrorKind {
    /// Service is unreachable (max retries/rapid disconnects exhausted at startup)
    Offline,
    /// Connection keeps dropping (max rapid disconnects exhausted after stable connection)
    Unstable,
    /// Initial connection failed (auth, config, network error)
    ConnectionFailed,
}

/// Summary information about a connector
///
/// Provides a lightweight view of a connector's essential properties.
/// Used by list operations and UI display without requiring full connector details.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ConnectorSummary {
    /// Unique connector identifier
    pub id: ConnectorId,
    /// Type of connector
    pub kind: ConnectorKind,
    /// User who owns this connector
    pub owner: UserId,
    /// Human-readable title for display
    pub title: String,
    /// Current operational state
    pub state: ConnectorState,
    /// Working directory for this connector
    #[serde(skip_serializing_if = "Option::is_none")]
    pub working_directory: Option<String>,
    /// Supported features for this connector (e.g., "cancellation", "session_resume")
    #[serde(default)]
    pub supported_features: Vec<String>,
    /// T034: Optional custom icon path for this connector
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon_path: Option<String>,
    /// T035: Show connector type emoji as overlay on custom icon
    #[serde(default)]
    pub show_type_overlay: bool,
    /// Whether archiving can be toggled for sessions on this connector
    ///
    /// When `false`, the UI should show archiving status as read-only (non-clickable).
    /// When `true`, the user can toggle archiving on/off for sessions.
    #[serde(default = "default_archiving_toggleable")]
    pub archiving_toggleable: bool,
    /// Agent type for automatic mode/model mapping (Claude, Codex, Gemini, or Custom)
    ///
    /// This is only set for ACP connectors and indicates the specific agent
    /// implementation behind the connector. Used during session transfers to
    /// automatically map Gateway mode/model identifiers to agent-specific ones.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub agent_type: Option<crate::connectors::acp::config::ConnectorAgentType>,

    /// Tool configuration for this connector.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_configuration: Option<crate::tools::ToolConfiguration>,

    /// Plugin assignments for this connector.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub plugin_assignments: Vec<crate::plugins::PluginAssignment>,

    /// Project assignments for this connector.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub project_assignments: Vec<crate::plugins::ProjectAssignment>,

    /// Whether this connector is used in newly created projects by default.
    #[serde(default = "default_true")]
    pub use_in_new_projects: bool,

    /// Source of this connector (e.g., "user", "zed").
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,

    /// Zed agent name this connector was created from.
    ///
    /// When set, identifies this connector as Zed-managed and enables
    /// automatic binary path refresh when Zed upgrades agents.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub zed_agent_name: Option<String>,

    /// Structured error classification when state is Error
    ///
    /// Provides machine-readable error type for UI indicators and capability computation.
    /// Only set when `state` is `Error(_)`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error_kind: Option<ConnectorErrorKind>,
}

fn default_true() -> bool {
    true
}

fn default_archiving_toggleable() -> bool {
    true
}

impl ConnectorSummary {
    /// Check if this connector supports cancellation
    pub fn supports_cancellation(&self) -> bool {
        self.supported_features.iter().any(|f| f == "cancellation")
    }

    /// Check if this connector supports session resume
    pub fn supports_session_resume(&self) -> bool {
        self.supported_features
            .iter()
            .any(|f| f == "session_resume")
    }

    /// Check if this connector supports listing sessions from the connector API
    ///
    /// When false, session listing falls back to archived sessions only.
    /// Connectors that do not expose a session list endpoint (e.g., ACP agents
    /// that only support creating new sessions) should not advertise this feature.
    pub fn supports_session_list(&self) -> bool {
        self.supported_features
            .iter()
            .any(|f| f == "session_list")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_type_aliases() {
        let connector_id: ConnectorId = "conn-001".to_string();
        let user_id: UserId = uuid::Uuid::nil();
        assert_eq!(connector_id, "conn-001");
        assert_eq!(user_id, uuid::Uuid::nil());
    }

    #[test]
    fn test_user_struct() {
        let user = User::new(UserProfile {
            name: Some("Test User".to_string()),
            ..Default::default()
        });
        assert_eq!(user.display_name(), "Test User");
    }

    #[test]
    fn test_connector_kind_enum() {
        let open_code = ConnectorKind::OpenCode;
        let acp = ConnectorKind::Acp;
        let mock = ConnectorKind::Mock;

        assert_eq!(open_code, ConnectorKind::OpenCode);
        assert_ne!(open_code, acp);
        assert_ne!(acp, mock);
    }

    #[test]
    fn test_connector_state_enum() {
        let _initializing = ConnectorState::Initializing;
        let _connecting = ConnectorState::Connecting;
        let ready = ConnectorState::Ready;
        let error = ConnectorState::Error("Connection failed".to_string());
        let stopped = ConnectorState::Stopped;

        assert_eq!(ready, ConnectorState::Ready);
        assert_eq!(
            error,
            ConnectorState::Error("Connection failed".to_string())
        );
        assert_ne!(ready, stopped);
    }

    #[test]
    fn test_connector_summary_struct() {
        let summary = ConnectorSummary {
            id: "conn-001".to_string(),
            kind: ConnectorKind::OpenCode,
            owner: uuid::Uuid::nil(),
            title: "My Connector".to_string(),
            state: ConnectorState::Ready,
            working_directory: None,
            supported_features: vec!["cancellation".to_string()],
            icon_path: None,
            show_type_overlay: false,
            archiving_toggleable: true,
            agent_type: None,
            tool_configuration: None,
            plugin_assignments: vec![],
            project_assignments: vec![],
            use_in_new_projects: true,
            source: None,
            zed_agent_name: None,
            error_kind: None,
        };

        assert_eq!(summary.id, "conn-001");
        assert_eq!(summary.kind, ConnectorKind::OpenCode);
        assert_eq!(summary.owner, uuid::Uuid::nil());
        assert_eq!(summary.title, "My Connector");
        assert_eq!(summary.state, ConnectorState::Ready);
        assert!(summary.supports_cancellation());
        assert!(summary.archiving_toggleable);
    }

    #[test]
    fn test_serialization() {
        let user = User::new(UserProfile {
            name: Some("Test User".to_string()),
            ..Default::default()
        });

        // Test serialization
        let json = serde_json::to_string(&user).expect("Failed to serialize");
        assert!(json.contains("Test User"));

        // Test deserialization
        let deserialized: User = serde_json::from_str(&json).expect("Failed to deserialize");
        assert_eq!(deserialized.id, user.id);
        assert_eq!(deserialized.display_name(), user.display_name());
    }

    #[test]
    fn test_connector_summary_serialization() {
        use crate::connectors::acp::config::ConnectorAgentType;

        let summary = ConnectorSummary {
            id: "conn-001".to_string(),
            kind: ConnectorKind::Acp,
            owner: uuid::Uuid::nil(),
            title: "ACP Connector".to_string(),
            state: ConnectorState::Connecting,
            working_directory: None,
            supported_features: vec!["session_resume".to_string()],
            icon_path: Some("/path/to/icon.png".to_string()),
            show_type_overlay: true,
            archiving_toggleable: true,
            agent_type: Some(ConnectorAgentType::Claude),
            tool_configuration: None,
            plugin_assignments: vec![],
            project_assignments: vec![],
            use_in_new_projects: true,
            source: None,
            zed_agent_name: None,
            error_kind: None,
        };

        let json = serde_json::to_string(&summary).expect("Failed to serialize");
        let deserialized: ConnectorSummary =
            serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(deserialized.id, summary.id);
        assert_eq!(deserialized.kind, summary.kind);
        assert_eq!(deserialized.owner, summary.owner);
        assert_eq!(deserialized.title, summary.title);
        assert_eq!(deserialized.state, summary.state);
        assert_eq!(deserialized.supported_features, summary.supported_features);
        assert_eq!(deserialized.icon_path, summary.icon_path);
        assert_eq!(deserialized.show_type_overlay, summary.show_type_overlay);
        assert_eq!(
            deserialized.archiving_toggleable,
            summary.archiving_toggleable
        );
        assert_eq!(deserialized.agent_type, summary.agent_type);
        assert!(deserialized.supports_session_resume());
        assert!(!deserialized.supports_cancellation());
    }

    #[test]
    fn test_archiving_toggleable_default() {
        // Test that archiving_toggleable defaults to true when not specified in JSON
        let json = r#"{
            "id": "conn-001",
            "kind": "OpenCode",
            "owner": "00000000-0000-0000-0000-000000000000",
            "title": "Test Connector",
            "state": "Ready",
            "supported_features": []
        }"#;

        let deserialized: ConnectorSummary =
            serde_json::from_str(json).expect("Failed to deserialize");
        assert!(
            deserialized.archiving_toggleable,
            "archiving_toggleable should default to true"
        );
    }

    #[test]
    fn test_supports_session_transfer() {
        assert!(ConnectorKind::OpenCode.supports_session_transfer());
        assert!(ConnectorKind::Acp.supports_session_transfer());
        assert!(ConnectorKind::Gateway.supports_session_transfer());
        assert!(!ConnectorKind::Mock.supports_session_transfer());
        assert!(!ConnectorKind::Acceptor.supports_session_transfer());
    }
}
