#![allow(dead_code)]
use super::TabId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MeetingStatus {
Active,
Paused,
Ended,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MeetingMessageType {
Regular,
Question,
Answer,
Proposal,
Agreement,
Objection,
Summary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeetingMessage {
pub id: u64,
pub sender: TabId,
pub content: String,
pub message_type: MeetingMessageType,
pub timestamp: DateTime<Utc>,
}
impl MeetingMessage {
pub fn new(id: u64, sender: TabId, content: String) -> Self {
Self {
id,
sender,
content,
message_type: MeetingMessageType::Regular,
timestamp: Utc::now(),
}
}
pub fn question(id: u64, sender: TabId, content: String) -> Self {
Self {
id,
sender,
content,
message_type: MeetingMessageType::Question,
timestamp: Utc::now(),
}
}
pub fn proposal(id: u64, sender: TabId, content: String) -> Self {
Self {
id,
sender,
content,
message_type: MeetingMessageType::Proposal,
timestamp: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeetingDecision {
pub id: u64,
pub description: String,
pub proposer: TabId,
pub supporters: Vec<TabId>,
pub timestamp: DateTime<Utc>,
}
impl MeetingDecision {
pub fn new(id: u64, description: String, proposer: TabId) -> Self {
Self {
id,
description,
proposer,
supporters: vec![proposer],
timestamp: Utc::now(),
}
}
pub fn add_supporter(&mut self, tab_id: TabId) {
if !self.supporters.contains(&tab_id) {
self.supporters.push(tab_id);
}
}
pub fn support_count(&self) -> usize {
self.supporters.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Meeting {
pub id: String,
pub topic: String,
pub participants: Vec<TabId>,
pub messages: Vec<MeetingMessage>,
pub decisions: Vec<MeetingDecision>,
pub status: MeetingStatus,
pub created_at: DateTime<Utc>,
pub ended_at: Option<DateTime<Utc>>,
}
impl Meeting {
pub fn new(id: String, topic: String, participants: Vec<TabId>) -> Self {
Self {
id,
topic,
participants,
messages: Vec::new(),
decisions: Vec::new(),
status: MeetingStatus::Active,
created_at: Utc::now(),
ended_at: None,
}
}
pub fn add_message(&mut self, msg: MeetingMessage) {
self.messages.push(msg);
}
pub fn add_decision(&mut self, decision: MeetingDecision) {
self.decisions.push(decision);
}
pub fn end(&mut self) {
self.status = MeetingStatus::Ended;
self.ended_at = Some(Utc::now());
}
pub fn duration(&self) -> chrono::Duration {
let end = self.ended_at.unwrap_or_else(Utc::now);
end - self.created_at
}
pub fn message_count(&self) -> usize {
self.messages.len()
}
pub fn decision_count(&self) -> usize {
self.decisions.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeetingSummary {
pub id: String,
pub topic: String,
pub participant_count: usize,
pub message_count: usize,
pub decision_count: usize,
pub duration_seconds: i64,
pub decisions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeetingHistory {
pub summary: MeetingSummary,
pub created_at: DateTime<Utc>,
}
pub struct MeetingManager {
active_meetings: HashMap<String, Meeting>,
history: Vec<MeetingHistory>,
next_meeting_id: u64,
next_message_id: u64,
next_decision_id: u64,
}
impl MeetingManager {
pub fn new() -> Self {
Self {
active_meetings: HashMap::new(),
history: Vec::new(),
next_meeting_id: 1,
next_message_id: 1,
next_decision_id: 1,
}
}
pub fn start_meeting(&mut self, topic: String, participants: Vec<TabId>) -> Option<String> {
if participants.len() < 2 {
return None;
}
let meeting_id = self.generate_meeting_id();
let meeting = Meeting::new(meeting_id.clone(), topic, participants);
self.active_meetings.insert(meeting_id.clone(), meeting);
Some(meeting_id)
}
pub fn get_meeting(&self, meeting_id: &str) -> Option<&Meeting> {
self.active_meetings.get(meeting_id)
}
pub fn get_meeting_mut(&mut self, meeting_id: &str) -> Option<&mut Meeting> {
self.active_meetings.get_mut(meeting_id)
}
pub fn add_message(&mut self, meeting_id: &str, msg: MeetingMessage) {
if let Some(meeting) = self.active_meetings.get_mut(meeting_id) {
meeting.add_message(msg);
}
}
pub fn create_message(
&mut self,
meeting_id: &str,
sender: TabId,
content: String,
) -> Option<u64> {
let msg_id = self.next_message_id;
self.next_message_id += 1;
if let Some(meeting) = self.active_meetings.get_mut(meeting_id) {
let msg = MeetingMessage::new(msg_id, sender, content);
meeting.add_message(msg);
Some(msg_id)
} else {
None
}
}
pub fn add_decision(&mut self, meeting_id: &str, decision: MeetingDecision) {
if let Some(meeting) = self.active_meetings.get_mut(meeting_id) {
meeting.add_decision(decision);
}
}
pub fn create_decision(
&mut self,
meeting_id: &str,
description: String,
proposer: TabId,
) -> Option<u64> {
let decision_id = self.next_decision_id;
self.next_decision_id += 1;
if let Some(meeting) = self.active_meetings.get_mut(meeting_id) {
let decision = MeetingDecision::new(decision_id, description, proposer);
meeting.add_decision(decision);
Some(decision_id)
} else {
None
}
}
pub fn end_meeting(&mut self, meeting_id: &str) -> Option<MeetingSummary> {
if let Some(mut meeting) = self.active_meetings.remove(meeting_id) {
meeting.end();
let duration = meeting.duration();
let summary = MeetingSummary {
id: meeting.id.clone(),
topic: meeting.topic.clone(),
participant_count: meeting.participants.len(),
message_count: meeting.message_count(),
decision_count: meeting.decision_count(),
duration_seconds: duration.num_seconds(),
decisions: meeting
.decisions
.iter()
.map(|d| d.description.clone())
.collect(),
};
self.history.push(MeetingHistory {
summary: summary.clone(),
created_at: meeting.created_at,
});
Some(summary)
} else {
None
}
}
pub fn active_meeting_for(&self, tab_id: TabId) -> Option<&Meeting> {
self.active_meetings
.values()
.find(|m| m.participants.contains(&tab_id))
}
pub fn active_meetings(&self) -> Vec<&Meeting> {
self.active_meetings.values().collect()
}
pub fn history(&self) -> &[MeetingHistory] {
&self.history
}
pub fn recent_meetings(&self, limit: usize) -> Vec<&MeetingHistory> {
let mut history: Vec<&MeetingHistory> = self.history.iter().collect();
history.sort_by_key(|m| std::cmp::Reverse(m.created_at));
history.into_iter().take(limit).collect()
}
pub fn is_in_meeting(&self, tab_id: TabId) -> bool {
self.active_meetings
.values()
.any(|m| m.participants.contains(&tab_id))
}
fn generate_meeting_id(&mut self) -> String {
let id = self.next_meeting_id;
self.next_meeting_id += 1;
format!("meeting_{}", id)
}
}
impl Default for MeetingManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_meeting_lifecycle() {
let mut manager = MeetingManager::new();
let tab1 = TabId::new(1);
let tab2 = TabId::new(2);
let meeting_id = manager
.start_meeting("Discuss design".to_string(), vec![tab1, tab2])
.unwrap();
assert!(manager.is_in_meeting(tab1));
assert!(manager.is_in_meeting(tab2));
assert!(!manager.is_in_meeting(TabId::new(3)));
manager.create_message(&meeting_id, tab1, "Let's start".to_string());
manager.create_message(&meeting_id, tab2, "Agreed".to_string());
let meeting = manager.get_meeting(&meeting_id).unwrap();
assert_eq!(meeting.message_count(), 2);
manager.create_decision(&meeting_id, "Use component pattern".to_string(), tab1);
let summary = manager.end_meeting(&meeting_id).unwrap();
assert_eq!(summary.topic, "Discuss design");
assert_eq!(summary.participant_count, 2);
assert_eq!(summary.message_count, 2);
assert_eq!(summary.decision_count, 1);
}
}