//! Type-safe identifiers and validated types for the domain model.
//!
//! These newtypes ensure compile-time safety against mixing different kinds of IDs
//! and provide validation for constrained types like NonEmptyString.

use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;

/// Session identifier - unique per session.
///
/// Sessions are the top-level container for conversations and tool state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct SessionId(#[schemars(with = "String")] pub Uuid);

impl SessionId {
    /// Create a new random session ID.
    ///
    /// # Determinism
    /// Only call from runtime at input boundary, never within reducer logic.
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from an existing UUID (for hydration/deserialization).
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    pub fn as_uuid(&self) -> Uuid {
        self.0
    }

    pub fn parse(s: &str) -> Option<Self> {
        Uuid::parse_str(s).ok().map(Self)
    }
}

impl Default for SessionId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for SessionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Uuid> for SessionId {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

impl From<SessionId> for Uuid {
    fn from(id: SessionId) -> Self {
        id.0
    }
}

/// Operation identifier - unique per user-initiated operation.
///
/// An operation represents a single user action (e.g., sending a message,
/// running a command) and all the work that follows from it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OpId(pub Uuid);

impl OpId {
    /// Create a new random operation ID.
    ///
    /// # Determinism
    /// Only call from runtime at input boundary, never within reducer logic.
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from an existing UUID (for hydration/deserialization).
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    /// Get the inner UUID value.
    pub fn as_uuid(&self) -> Uuid {
        self.0
    }
}

impl Default for OpId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for OpId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Uuid> for OpId {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

impl From<OpId> for Uuid {
    fn from(id: OpId) -> Self {
        id.0
    }
}

/// Message identifier - unique per message in a conversation.
///
/// Messages form a tree structure (for branching conversations) linked by parent IDs.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MessageId(pub String);

impl MessageId {
    /// Create a new random message ID.
    ///
    /// # Determinism
    /// Only call from runtime at input boundary, never within reducer logic.
    pub fn new() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Create from an existing string (for hydration/deserialization).
    pub fn from_string(s: impl Into<String>) -> Self {
        Self(s.into())
    }

    /// Get the inner string value.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Default for MessageId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for MessageId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<String> for MessageId {
    fn from(s: String) -> Self {
        Self(s)
    }
}

impl From<&str> for MessageId {
    fn from(s: &str) -> Self {
        Self(s.to_string())
    }
}

impl From<MessageId> for String {
    fn from(id: MessageId) -> Self {
        id.0
    }
}

/// Tool call identifier - unique per tool call.
///
/// These IDs are typically generated by the LLM API and used to match
/// tool results back to their corresponding tool calls.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ToolCallId(pub String);

impl ToolCallId {
    /// Create a new random tool call ID.
    ///
    /// # Determinism
    /// Only call from runtime at input boundary, never within reducer logic.
    pub fn new() -> Self {
        Self(format!("tool_{}", Uuid::new_v4()))
    }

    /// Create from an existing string (typically from LLM API).
    pub fn from_string(s: impl Into<String>) -> Self {
        Self(s.into())
    }

    /// Get the inner string value.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Default for ToolCallId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for ToolCallId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<String> for ToolCallId {
    fn from(s: String) -> Self {
        Self(s)
    }
}

impl From<&str> for ToolCallId {
    fn from(s: &str) -> Self {
        Self(s.to_string())
    }
}

impl From<ToolCallId> for String {
    fn from(id: ToolCallId) -> Self {
        id.0
    }
}

/// Approval request identifier - unique per tool approval request.
///
/// Used to track pending approval dialogs and match user decisions
/// back to the original request.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RequestId(pub Uuid);

impl RequestId {
    /// Create a new random request ID.
    ///
    /// # Determinism
    /// Only call from runtime at input boundary, never within reducer logic.
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from an existing UUID (for hydration/deserialization).
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    /// Get the inner UUID value.
    pub fn as_uuid(&self) -> Uuid {
        self.0
    }
}

impl Default for RequestId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for RequestId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Uuid> for RequestId {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

impl From<RequestId> for Uuid {
    fn from(id: RequestId) -> Self {
        id.0
    }
}

/// Validated non-empty string.
///
/// Ensures that user input contains actual content, not just whitespace.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct NonEmptyString(String);

impl NonEmptyString {
    /// Create a new NonEmptyString if the input is not empty or whitespace-only.
    pub fn new(s: impl Into<String>) -> Option<Self> {
        let s = s.into();
        if s.trim().is_empty() {
            None
        } else {
            Some(Self(s))
        }
    }

    /// Get the inner string as a reference.
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Get the inner string value.
    pub fn into_inner(self) -> String {
        self.0
    }
}

impl fmt::Display for NonEmptyString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl TryFrom<String> for NonEmptyString {
    type Error = &'static str;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::new(value).ok_or("string must not be empty")
    }
}

impl From<NonEmptyString> for String {
    fn from(s: NonEmptyString) -> Self {
        s.0
    }
}

impl AsRef<str> for NonEmptyString {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

/// Compaction identifier - unique per conversation compaction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct CompactionId(pub Uuid);

impl CompactionId {
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    pub fn as_uuid(&self) -> Uuid {
        self.0
    }
}

impl Default for CompactionId {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Display for CompactionId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl From<Uuid> for CompactionId {
    fn from(uuid: Uuid) -> Self {
        Self(uuid)
    }
}

impl From<CompactionId> for Uuid {
    fn from(id: CompactionId) -> Self {
        id.0
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompactionRecord {
    pub id: CompactionId,
    pub summary_message_id: MessageId,
    pub compacted_head_message_id: MessageId,
    pub previous_active_message_id: Option<MessageId>,
    pub model: String,
    pub created_at: DateTime<Utc>,
}

impl CompactionRecord {
    pub fn new(
        id: CompactionId,
        summary_message_id: MessageId,
        compacted_head_message_id: MessageId,
        previous_active_message_id: Option<MessageId>,
        model: String,
    ) -> Self {
        Self {
            id,
            summary_message_id,
            compacted_head_message_id,
            previous_active_message_id,
            model,
            created_at: Utc::now(),
        }
    }

    pub fn with_timestamp(
        id: CompactionId,
        summary_message_id: MessageId,
        compacted_head_message_id: MessageId,
        previous_active_message_id: Option<MessageId>,
        model: String,
        timestamp: u64,
    ) -> Self {
        Self {
            id,
            summary_message_id,
            compacted_head_message_id,
            previous_active_message_id,
            model,
            created_at: DateTime::from_timestamp(timestamp as i64, 0).unwrap_or_else(Utc::now),
        }
    }
}

/// Unix timestamp in seconds since epoch.
pub type Timestamp = u64;

/// Event sequence number for ordering and replay.
///
/// Monotonically increasing within a session.
pub type SequenceNumber = u64;

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

    #[test]
    fn test_session_id_equality() {
        let uuid = Uuid::new_v4();
        let id1 = SessionId::from_uuid(uuid);
        let id2 = SessionId::from_uuid(uuid);
        assert_eq!(id1, id2);
    }

    #[test]
    fn test_session_id_uniqueness() {
        let id1 = SessionId::new();
        let id2 = SessionId::new();
        assert_ne!(id1, id2);
    }

    #[test]
    fn test_op_id_display() {
        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
        let id = OpId::from_uuid(uuid);
        assert_eq!(id.to_string(), "550e8400-e29b-41d4-a716-446655440000");
    }

    #[test]
    fn test_message_id_from_string() {
        let id = MessageId::from_string("msg_123");
        assert_eq!(id.as_str(), "msg_123");
    }

    #[test]
    fn test_tool_call_id_prefix() {
        let id = ToolCallId::new();
        assert!(id.as_str().starts_with("tool_"));
    }

    #[test]
    fn test_non_empty_string_valid() {
        let s = NonEmptyString::new("hello").unwrap();
        assert_eq!(s.as_str(), "hello");
    }

    #[test]
    fn test_non_empty_string_empty() {
        assert!(NonEmptyString::new("").is_none());
    }

    #[test]
    fn test_non_empty_string_whitespace_only() {
        assert!(NonEmptyString::new("   ").is_none());
        assert!(NonEmptyString::new("\t\n").is_none());
    }

    #[test]
    fn test_non_empty_string_preserves_whitespace() {
        let s = NonEmptyString::new("  hello  ").unwrap();
        assert_eq!(s.as_str(), "  hello  ");
    }

    #[test]
    fn test_request_id_serialization() {
        let id = RequestId::new();
        let json = serde_json::to_string(&id).unwrap();
        let deserialized: RequestId = serde_json::from_str(&json).unwrap();
        assert_eq!(id, deserialized);
    }

    #[test]
    fn test_non_empty_string_serde() {
        let original = NonEmptyString::new("test").unwrap();
        let json = serde_json::to_string(&original).unwrap();
        assert_eq!(json, "\"test\"");

        let deserialized: NonEmptyString = serde_json::from_str(&json).unwrap();
        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_non_empty_string_serde_empty_fails() {
        let result: Result<NonEmptyString, _> = serde_json::from_str("\"\"");
        assert!(result.is_err());
    }
}
