package service import ( "context" "fmt" "time" "github.com/orchard9/rdev/internal/domain" "github.com/orchard9/rdev/internal/logging" "github.com/orchard9/rdev/internal/port" ) // ConversationService orchestrates conversation operations. type ConversationService struct { repo port.ConversationRepository } // NewConversationService creates a new conversation service. func NewConversationService(repo port.ConversationRepository) *ConversationService { return &ConversationService{repo: repo} } // CreateConversation creates a new conversation. func (s *ConversationService) CreateConversation(ctx context.Context, projectID, title string) (*domain.Conversation, error) { if projectID == "" { return nil, fmt.Errorf("project_id is required") } if title == "" { title = "New Conversation" } startTime := time.Now() conv, err := s.repo.CreateConversation(ctx, projectID, title) if err != nil { return nil, err } log := logging.FromContext(ctx) log.Info("conversation created", "conversation_id", conv.ID, logging.FieldProjectID, projectID, logging.FieldOperation, "create_conversation", logging.FieldDuration, time.Since(startTime).Milliseconds(), "title", title, ) return conv, nil } // GetConversation retrieves a conversation by ID. func (s *ConversationService) GetConversation(ctx context.Context, id domain.ConversationID) (*domain.Conversation, error) { return s.repo.GetConversation(ctx, id) } // ListConversations returns all conversations for a project. func (s *ConversationService) ListConversations(ctx context.Context, projectID string) ([]*domain.Conversation, error) { return s.repo.ListConversations(ctx, projectID) } // UpdateTitle updates the conversation title. func (s *ConversationService) UpdateTitle(ctx context.Context, id domain.ConversationID, title string) error { if title == "" { return fmt.Errorf("title is required") } return s.repo.UpdateConversationTitle(ctx, id, title) } // DeleteConversation deletes a conversation and all its messages. func (s *ConversationService) DeleteConversation(ctx context.Context, id domain.ConversationID) error { startTime := time.Now() if err := s.repo.DeleteConversation(ctx, id); err != nil { return err } log := logging.FromContext(ctx) log.Info("conversation deleted", "conversation_id", id, logging.FieldOperation, "delete_conversation", logging.FieldDuration, time.Since(startTime).Milliseconds(), ) return nil } // AddMessage adds a message to a conversation. func (s *ConversationService) AddMessage(ctx context.Context, conversationID domain.ConversationID, role domain.MessageRole, content string) (*domain.Message, error) { if content == "" { return nil, fmt.Errorf("message content is required") } startTime := time.Now() msg, err := s.repo.AddMessage(ctx, conversationID, role, content) if err != nil { return nil, err } log := logging.FromContext(ctx) log.Info("message added", "conversation_id", conversationID, "message_id", msg.ID, "role", role, logging.FieldOperation, "add_message", logging.FieldDuration, time.Since(startTime).Milliseconds(), "content_length", len(content), ) return msg, nil } // GetMessages retrieves all messages for a conversation. func (s *ConversationService) GetMessages(ctx context.Context, conversationID domain.ConversationID) ([]*domain.Message, error) { return s.repo.GetMessages(ctx, conversationID) } // GetConversationWithMessages retrieves a conversation with all messages. func (s *ConversationService) GetConversationWithMessages(ctx context.Context, id domain.ConversationID) (*domain.ConversationWithMessages, error) { return s.repo.GetConversationWithMessages(ctx, id) }