summaryrefslogblamecommitdiff
path: root/makima/src/db/models.rs
blob: 97657dca69e43eabf4c0ad4df745efe329b15ca8 (plain) (tree)
1
2
3
4
5
6
7
8







                                        




                                                             



































                                                                                                                                           











                                                                      

















                                                         









                                         













                                          

                                                                               

 





                                                     



                                                                               




                                         




                                                              

                                             





                                                                            







                                            

                                                                                  



                                                         

                                             


                                                                






                                                                      











                                                  



                                          

                                                                           

                                                          














                                                                





                                                   




                                                      

                                             



                                         












                                     


                                                                                



                                                                         
                                  

                                                    




                                        































































































                                                                                

























































































                                                                                

                                                              
                                     
                                                       






                                    




                                                                                 
























                                                             























                                                                 














                                                                              













                                                                            





                                                                                    





                                                                    







                                                                   


















                                                                        





                                                    

                                                                                        








                                                                            


                                          


                                                                   



                                  







                                                                     
                                                                      








                                                          
                                





                                        











                                           

                                                                             







                                             


                                                                                 



















                                                                             

                                                



                                                                                 


                                                                                                      



                                                                   















                                                

                                                                                   








                                                             

                                                                   




















                                                        






























                                                                                    




























































































































































































































                                                                                                








































                                                                                




































































































                                                                                

                                                                                





































































































                                                                                            


                                                                                





                                                                              
                                                 

                                                




                                                      
                  



                                                              












                                                                        
                                                          










                                                               
                                                   




                                                              























































































































































                                                                              

                                                  




                                                                        




                                                                                   




                                                                                      




                                                                             




                                                                                      




                                                                                                 
                                                                                 
                                                                       
                                                               
                                                     
                                                





                                  




                                                                      








                                                                 
 


                                                          
                                                       





                                                                    
                                           
                                                                        
                                    




                                       
              

















                                                                              
                                                       







                                                      

         
 
                                                                            
                                                  







                                                                    
                                                       








                                                                    
                                           

                                                    




                                                                                             










                                                              



                                                                 
     


















                                                                                      




































                                                                            

                                                  

                       

                                                     


                                                                                            


                                                                                                

































                                               

                                                                                                 

                                                                         

                                                                           

                                      




                                                                                                

                                               

                                      




                                                                              




                                                                                      




                                                                                      




                                                                                                 












                                                     


                                                                          




                                                                                      




                                                                                      




                                                                                                 





































                                                                  






                                                                               






























                                                                               





































                                                             





























































































                                                                               











































                                                                                      











                                                        

























































                                                            



































                                                                               





























                                                                                  







                                                           













































































                                                                                
                                                                          
                                    
                                                                          

























































































































































                                                                   









































                                                                               




                                                                               




































































































                                                                                                










































































                                                                               
 






























































































                                                                                                                      

                                                                                
















                                                                                           


                                         

                                                                                                                            






                                          









                                                                  












                                                                   




                                                                                   


























                                                     

                                         

                                                                                                                            

























                                          

                                                               












                                                

                                  

                                                               









                                                                
                                                  





                                     







                                                  








                                         











                                       


                                                             



                                                                               













                                                
 




                                                                                 

                                                                             








                                                                  
                                                               




                                                             
                                                                            


                                        

                                                                                    

                                       

                                                                  














                                              

                                                
                                       

                                                                     


















                                                
                                       

                                                                              













                                        
                                                                                          







                                                                                      

                                                

                                                                                    








                                                     












































                                                                                         
 
//! Database models for the files table.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use utoipa::ToSchema;
use uuid::Uuid;

/// Default max retries for task daemon failover (3 attempts)
fn default_max_retries() -> i32 {
    3
}

/// Flexible datetime deserialization module.
/// Accepts both date-only ("2026-01-15") and full ISO 8601 datetime ("2026-01-15T00:00:00Z") formats.
pub mod flexible_datetime {
    use chrono::{DateTime, NaiveDate, NaiveTime, TimeZone, Utc};
    use serde::{self, Deserialize, Deserializer};

    /// Deserializes a datetime from either date-only or full datetime format.
    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: Option<String> = Option::deserialize(deserializer)?;
        match s {
            None => Ok(None),
            Some(s) if s.is_empty() => Ok(None),
            Some(s) => {
                // Try full datetime first (RFC 3339 / ISO 8601)
                if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
                    return Ok(Some(dt.with_timezone(&Utc)));
                }

                // Try date-only format (YYYY-MM-DD) and convert to start of day UTC
                if let Ok(date) = NaiveDate::parse_from_str(&s, "%Y-%m-%d") {
                    let datetime = date.and_time(NaiveTime::MIN);
                    return Ok(Some(Utc.from_utc_datetime(&datetime)));
                }

                Err(serde::de::Error::custom(format!(
                    "Invalid datetime format '{}'. Expected ISO 8601 datetime (e.g., '2026-01-15T00:00:00Z') or date (e.g., '2026-01-15')",
                    s
                )))
            }
        }
    }
}

/// TranscriptEntry stored in JSONB - matches frontend TranscriptEntry
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TranscriptEntry {
    pub id: String,
    pub speaker: String,
    pub start: f32,
    pub end: f32,
    pub text: String,
    pub is_final: bool,
}

/// Chart type for visualization elements
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ChartType {
    Line,
    Bar,
    Pie,
    Area,
}

/// Body element types for structured file content
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum BodyElement {
    /// Heading element (h1-h6)
    Heading { level: u8, text: String },
    /// Paragraph text
    Paragraph { text: String },
    /// Code block with optional language
    Code {
        language: Option<String>,
        content: String,
    },
    /// List (ordered or unordered)
    List {
        ordered: bool,
        items: Vec<String>,
    },
    /// Chart visualization
    Chart {
        #[serde(rename = "chartType")]
        chart_type: ChartType,
        title: Option<String>,
        data: serde_json::Value,
        config: Option<serde_json::Value>,
    },
    /// Image element (deferred for MVP)
    Image {
        src: String,
        alt: Option<String>,
        caption: Option<String>,
    },
    /// Raw markdown content - renders as formatted markdown, edits as raw text
    Markdown { content: String },
}

/// File record from the database.
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct File {
    pub id: Uuid,
    pub owner_id: Uuid,
    /// Contract this file belongs to (optional)
    pub contract_id: Option<Uuid>,
    /// Phase of the contract when file was added (e.g., "research", "specify")
    pub contract_phase: Option<String>,
    pub name: String,
    pub description: Option<String>,
    #[sqlx(json)]
    pub transcript: Vec<TranscriptEntry>,
    pub location: Option<String>,
    /// AI-generated summary of the transcript
    pub summary: Option<String>,
    /// Structured body content (headings, paragraphs, charts)
    #[sqlx(json)]
    pub body: Vec<BodyElement>,
    /// Version number for optimistic locking
    pub version: i32,
    /// Path to linked repository file (e.g., "README.md", "docs/design.md")
    pub repo_file_path: Option<String>,
    /// When the file was last synced from the repository
    pub repo_synced_at: Option<DateTime<Utc>>,
    /// Sync status: 'none', 'synced', 'modified', 'conflict'
    pub repo_sync_status: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Request payload for creating a new file.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateFileRequest {
    /// Contract this file belongs to (required - files must belong to a contract)
    pub contract_id: Uuid,
    /// Name of the file (auto-generated if not provided)
    pub name: Option<String>,
    /// Optional description
    pub description: Option<String>,
    /// Transcript entries (default to empty)
    #[serde(default)]
    pub transcript: Vec<TranscriptEntry>,
    /// Storage location (e.g., s3://bucket/path) - not used yet
    pub location: Option<String>,
    /// Initial body elements (e.g., from a template)
    #[serde(default)]
    pub body: Vec<BodyElement>,
    /// Path to linked repository file (e.g., "README.md")
    pub repo_file_path: Option<String>,
    /// Contract phase this file belongs to (for deliverable tracking)
    pub contract_phase: Option<String>,
}

/// Request payload for updating an existing file.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateFileRequest {
    /// New name (optional)
    pub name: Option<String>,
    /// New description (optional)
    pub description: Option<String>,
    /// New transcript (optional)
    pub transcript: Option<Vec<TranscriptEntry>>,
    /// AI-generated summary (optional)
    pub summary: Option<String>,
    /// Structured body content (optional)
    pub body: Option<Vec<BodyElement>>,
    /// Version for optimistic locking (required for updates from frontend)
    pub version: Option<i32>,
    /// Path to linked repository file (e.g., "README.md")
    pub repo_file_path: Option<String>,
}

/// Response for file list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileListResponse {
    pub files: Vec<FileSummary>,
    pub total: i64,
}

/// Summary of a file for list views (excludes full transcript).
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileSummary {
    pub id: Uuid,
    /// Contract this file belongs to
    pub contract_id: Option<Uuid>,
    /// Contract name (joined from contracts table)
    pub contract_name: Option<String>,
    /// Phase when file was added to contract
    pub contract_phase: Option<String>,
    pub name: String,
    pub description: Option<String>,
    pub transcript_count: usize,
    /// Duration derived from last transcript end time
    pub duration: Option<f32>,
    /// Version number for optimistic locking
    pub version: i32,
    /// Path to linked repository file
    pub repo_file_path: Option<String>,
    /// Sync status with repository
    pub repo_sync_status: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl From<File> for FileSummary {
    fn from(file: File) -> Self {
        let duration = file
            .transcript
            .iter()
            .map(|t| t.end)
            .fold(0.0_f32, f32::max);
        Self {
            id: file.id,
            contract_id: file.contract_id,
            contract_name: None, // Not available from File alone, requires JOIN
            contract_phase: file.contract_phase,
            name: file.name,
            description: file.description,
            transcript_count: file.transcript.len(),
            duration: if duration > 0.0 { Some(duration) } else { None },
            version: file.version,
            repo_file_path: file.repo_file_path,
            repo_sync_status: file.repo_sync_status,
            created_at: file.created_at,
            updated_at: file.updated_at,
        }
    }
}

// =============================================================================
// Version History Types
// =============================================================================

/// Source of a version change
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, sqlx::Type)]
#[sqlx(type_name = "varchar")]
#[serde(rename_all = "lowercase")]
pub enum VersionSource {
    #[sqlx(rename = "user")]
    User,
    #[sqlx(rename = "llm")]
    Llm,
    #[sqlx(rename = "system")]
    System,
}

impl std::fmt::Display for VersionSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            VersionSource::User => write!(f, "user"),
            VersionSource::Llm => write!(f, "llm"),
            VersionSource::System => write!(f, "system"),
        }
    }
}

impl std::str::FromStr for VersionSource {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "user" => Ok(VersionSource::User),
            "llm" => Ok(VersionSource::Llm),
            "system" => Ok(VersionSource::System),
            _ => Err(format!("Unknown version source: {}", s)),
        }
    }
}

/// Full version record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileVersion {
    pub id: Uuid,
    pub file_id: Uuid,
    pub version: i32,
    pub name: String,
    pub description: Option<String>,
    pub summary: Option<String>,
    #[sqlx(json)]
    pub body: Vec<BodyElement>,
    pub source: String,
    pub change_description: Option<String>,
    pub created_at: DateTime<Utc>,
}

/// Summary of a version for list views
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileVersionSummary {
    pub version: i32,
    pub source: String,
    pub created_at: DateTime<Utc>,
    pub change_description: Option<String>,
}

impl From<FileVersion> for FileVersionSummary {
    fn from(v: FileVersion) -> Self {
        Self {
            version: v.version,
            source: v.source,
            created_at: v.created_at,
            change_description: v.change_description,
        }
    }
}

/// Response for version list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct FileVersionListResponse {
    pub versions: Vec<FileVersionSummary>,
    pub total: i64,
}

/// Request to restore a file to a previous version
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RestoreVersionRequest {
    /// The version to restore to
    pub target_version: i32,
    /// The current version (for optimistic locking)
    pub current_version: i32,
}

// =============================================================================
// Mesh/Task Types
// =============================================================================

/// Task status for orchestrating Claude Code instances
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
    Pending,
    Running,
    Paused,
    Blocked,
    Done,
    Failed,
    Merged,
}

impl std::fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TaskStatus::Pending => write!(f, "pending"),
            TaskStatus::Running => write!(f, "running"),
            TaskStatus::Paused => write!(f, "paused"),
            TaskStatus::Blocked => write!(f, "blocked"),
            TaskStatus::Done => write!(f, "done"),
            TaskStatus::Failed => write!(f, "failed"),
            TaskStatus::Merged => write!(f, "merged"),
        }
    }
}

impl std::str::FromStr for TaskStatus {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "pending" => Ok(TaskStatus::Pending),
            "running" => Ok(TaskStatus::Running),
            "paused" => Ok(TaskStatus::Paused),
            "blocked" => Ok(TaskStatus::Blocked),
            "done" => Ok(TaskStatus::Done),
            "failed" => Ok(TaskStatus::Failed),
            "merged" => Ok(TaskStatus::Merged),
            _ => Err(format!("Unknown task status: {}", s)),
        }
    }
}

/// Merge mode for task completion
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum MergeMode {
    /// Create a PR for review
    Pr,
    /// Auto-merge to target branch
    Auto,
    /// Manual merge by user
    Manual,
}

impl std::fmt::Display for MergeMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MergeMode::Pr => write!(f, "pr"),
            MergeMode::Auto => write!(f, "auto"),
            MergeMode::Manual => write!(f, "manual"),
        }
    }
}

impl std::str::FromStr for MergeMode {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "pr" => Ok(MergeMode::Pr),
            "auto" => Ok(MergeMode::Auto),
            "manual" => Ok(MergeMode::Manual),
            _ => Err(format!("Unknown merge mode: {}", s)),
        }
    }
}

/// Task record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Task {
    pub id: Uuid,
    pub owner_id: Uuid,
    /// Contract this task belongs to (required for new tasks)
    pub contract_id: Option<Uuid>,
    pub parent_task_id: Option<Uuid>,
    /// Depth in task hierarchy (no longer constrained)
    pub depth: i32,
    pub name: String,
    pub description: Option<String>,
    pub status: String,
    pub priority: i32,
    pub plan: String,

    // Supervisor flag
    /// True for contract supervisor tasks. Only supervisors can spawn new tasks.
    #[serde(default)]
    pub is_supervisor: bool,

    // Daemon/container info
    pub daemon_id: Option<Uuid>,
    pub container_id: Option<String>,
    pub overlay_path: Option<String>,

    // Repository info
    pub repository_url: Option<String>,
    pub base_branch: Option<String>,
    pub target_branch: Option<String>,

    // Merge settings
    pub merge_mode: Option<String>,
    pub pr_url: Option<String>,

    // Completion action settings
    /// Path to user's local repository (outside ~/.makima)
    pub target_repo_path: Option<String>,
    /// Action on completion: "none", "branch", "merge", "pr"
    pub completion_action: Option<String>,

    // Progress tracking
    pub progress_summary: Option<String>,
    pub last_output: Option<String>,
    pub error_message: Option<String>,

    // Git checkpoint tracking
    /// Git commit SHA of the most recent checkpoint
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_checkpoint_sha: Option<String>,
    /// Number of checkpoints created by this task
    #[serde(default)]
    pub checkpoint_count: i32,
    /// Message from the most recent checkpoint
    #[serde(skip_serializing_if = "Option::is_none")]
    pub checkpoint_message: Option<String>,

    // Conversation state for resumption
    /// Saved conversation context for task/supervisor resumption
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conversation_state: Option<serde_json::Value>,

    // Daemon migration tracking
    /// Previous daemon if task was migrated
    #[serde(skip_serializing_if = "Option::is_none")]
    pub migrated_from_daemon_id: Option<Uuid>,
    /// Most recent daemon that worked on this task
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_active_daemon_id: Option<Uuid>,

    // Timestamps
    pub started_at: Option<DateTime<Utc>>,
    pub completed_at: Option<DateTime<Utc>>,
    pub version: i32,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,

    // Task continuation
    /// Task ID to continue from (copy worktree from this task when starting).
    /// Used for sequential subtask dependencies.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub continue_from_task_id: Option<Uuid>,
    /// Files to copy from parent task's worktree when starting.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub copy_files: Option<serde_json::Value>,

    // Retry tracking for daemon failover
    /// Number of times this task has been retried after daemon failure
    #[serde(default)]
    pub retry_count: i32,
    /// Maximum retry attempts before marking as permanently failed
    #[serde(default = "default_max_retries")]
    pub max_retries: i32,
    /// Array of daemon IDs that have failed this task (excluded from retry)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub failed_daemon_ids: Option<Vec<Uuid>>,
    /// When the task was last interrupted due to daemon disconnect
    #[serde(skip_serializing_if = "Option::is_none")]
    pub interrupted_at: Option<DateTime<Utc>>,

    // Task branching
    /// Source task ID when this task was branched from another task's conversation.
    /// Used to track the origin of "what if" explorations.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub branched_from_task_id: Option<Uuid>,

    // UI visibility
    /// Whether this task is hidden from the UI (user dismissed it).
    /// Standalone completed tasks can be dismissed by the user.
    #[serde(default)]
    pub hidden: bool,

    // Directive association
    /// Directive this task belongs to (for directive-driven tasks)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub directive_id: Option<Uuid>,
    /// Directive step this task executes
    #[serde(skip_serializing_if = "Option::is_none")]
    pub directive_step_id: Option<Uuid>,
}

impl Task {
    /// Parse status string to TaskStatus enum
    pub fn status_enum(&self) -> Result<TaskStatus, String> {
        self.status.parse()
    }

    /// Parse merge_mode string to MergeMode enum
    pub fn merge_mode_enum(&self) -> Option<Result<MergeMode, String>> {
        self.merge_mode.as_ref().map(|s| s.parse())
    }
}

/// Summary of a task for list views
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskSummary {
    pub id: Uuid,
    /// Contract this task belongs to
    pub contract_id: Option<Uuid>,
    /// Contract name (joined from contracts table)
    pub contract_name: Option<String>,
    /// Contract phase (joined from contracts table)
    pub contract_phase: Option<String>,
    /// Contract status (joined from contracts table): 'active', 'completed', 'archived'
    pub contract_status: Option<String>,
    pub parent_task_id: Option<Uuid>,
    /// Depth in task hierarchy: 0=orchestrator (top-level), 1=subtask (max)
    pub depth: i32,
    pub name: String,
    pub status: String,
    pub priority: i32,
    pub progress_summary: Option<String>,
    pub subtask_count: i64,
    pub version: i32,
    /// True for contract supervisor tasks
    #[serde(default)]
    pub is_supervisor: bool,
    /// Whether this task is hidden from the UI (user dismissed it)
    #[serde(default)]
    pub hidden: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Convert a full Task to a TaskSummary
impl From<Task> for TaskSummary {
    fn from(task: Task) -> Self {
        Self {
            id: task.id,
            contract_id: task.contract_id,
            contract_name: None, // Not available from Task directly
            contract_phase: None, // Not available from Task directly
            contract_status: None, // Not available from Task directly
            parent_task_id: task.parent_task_id,
            depth: task.depth,
            name: task.name,
            status: task.status,
            priority: task.priority,
            progress_summary: task.progress_summary,
            subtask_count: 0, // Would need separate query
            version: task.version,
            is_supervisor: task.is_supervisor,
            hidden: task.hidden,
            created_at: task.created_at,
            updated_at: task.updated_at,
        }
    }
}

/// Response for task list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskListResponse {
    pub tasks: Vec<TaskSummary>,
    pub total: i64,
}

/// Request payload for creating a new task
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateTaskRequest {
    /// Contract this task belongs to (optional for branched/anonymous tasks)
    pub contract_id: Option<Uuid>,
    /// Name of the task
    pub name: String,
    /// Optional description
    pub description: Option<String>,
    /// The plan/instructions for Claude Code
    pub plan: String,
    /// Parent task ID (for subtasks)
    pub parent_task_id: Option<Uuid>,
    /// True for contract supervisor tasks. Only supervisors can spawn new tasks.
    #[serde(default)]
    pub is_supervisor: bool,
    /// Priority (higher = more urgent)
    #[serde(default)]
    pub priority: i32,
    /// Repository URL
    pub repository_url: Option<String>,
    /// Base branch for overlay
    pub base_branch: Option<String>,
    /// Target branch to merge into
    pub target_branch: Option<String>,
    /// Merge mode (pr, auto, manual)
    pub merge_mode: Option<String>,
    /// Path to user's local repository (outside ~/.makima)
    pub target_repo_path: Option<String>,
    /// Action on completion: "none", "branch", "merge", "pr"
    pub completion_action: Option<String>,
    /// Task ID to continue from (copy worktree from this task when starting)
    pub continue_from_task_id: Option<Uuid>,
    /// Files to copy from parent task's worktree when starting
    #[serde(skip_serializing_if = "Option::is_none")]
    pub copy_files: Option<Vec<String>>,
    /// Checkpoint SHA to branch from (optional)
    pub checkpoint_sha: Option<String>,
    /// Source task ID when branching from another task's conversation
    pub branched_from_task_id: Option<Uuid>,
    /// Conversation history to initialize the task with (JSON array of messages)
    pub conversation_history: Option<serde_json::Value>,
    /// Task ID whose worktree this task shares. When set, this task reuses the supervisor's
    /// worktree instead of creating its own, and should NOT have its worktree deleted during cleanup.
    pub supervisor_worktree_task_id: Option<Uuid>,
    /// Directive this task belongs to (for directive-driven tasks)
    pub directive_id: Option<Uuid>,
    /// Directive step this task executes
    pub directive_step_id: Option<Uuid>,
}

/// Request payload for updating a task
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTaskRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub plan: Option<String>,
    pub status: Option<String>,
    pub priority: Option<i32>,
    pub progress_summary: Option<String>,
    pub last_output: Option<String>,
    pub error_message: Option<String>,
    pub merge_mode: Option<String>,
    pub pr_url: Option<String>,
    /// Repository URL for the task (e.g., when updating supervisor with repo info)
    pub repository_url: Option<String>,
    /// Path to user's local repository (outside ~/.makima)
    pub target_repo_path: Option<String>,
    /// Action on completion: "none", "branch", "merge", "pr"
    pub completion_action: Option<String>,
    /// The daemon currently running this task
    pub daemon_id: Option<Uuid>,
    /// Explicitly clear daemon_id (set to NULL)
    #[serde(default)]
    pub clear_daemon_id: bool,
    /// Whether this task is hidden from the UI (user dismissed it)
    pub hidden: Option<bool>,
    /// Version for optimistic locking
    pub version: Option<i32>,
}

/// Task with its subtasks for detail view
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskWithSubtasks {
    #[serde(flatten)]
    pub task: Task,
    pub subtasks: Vec<TaskSummary>,
}

/// Request to send a message to a running task's stdin.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageRequest {
    /// The message to send to the task's stdin.
    pub message: String,
}

/// Default for include_conversation field in BranchTaskRequest
fn default_include_conversation() -> bool {
    true
}

/// Request to branch a task from an existing task's conversation.
/// Creates a new anonymous task that continues from the source task's state.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchTaskRequest {
    /// The initial message/instructions for the branched task
    pub message: String,
    /// Optional name for the branched task (auto-generated if not provided)
    pub name: Option<String>,
    /// Whether to include conversation history from the source task (default: true)
    #[serde(default = "default_include_conversation")]
    pub include_conversation: bool,
}

/// Response from branching a task.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchTaskResponse {
    /// The newly created branched task
    pub task: Task,
    /// Number of conversation messages included from source task
    pub message_count: usize,
    /// Daemon ID if the task was started (None if no daemon available)
    pub daemon_id: Option<Uuid>,
}

// =============================================================================
// Daemon Types
// =============================================================================

/// Daemon status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum DaemonStatus {
    Connected,
    Disconnected,
    Unhealthy,
}

impl std::fmt::Display for DaemonStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DaemonStatus::Connected => write!(f, "connected"),
            DaemonStatus::Disconnected => write!(f, "disconnected"),
            DaemonStatus::Unhealthy => write!(f, "unhealthy"),
        }
    }
}

impl std::str::FromStr for DaemonStatus {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "connected" => Ok(DaemonStatus::Connected),
            "disconnected" => Ok(DaemonStatus::Disconnected),
            "unhealthy" => Ok(DaemonStatus::Unhealthy),
            _ => Err(format!("Unknown daemon status: {}", s)),
        }
    }
}

/// Connected daemon record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Daemon {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub connection_id: String,
    pub hostname: Option<String>,
    pub machine_id: Option<String>,
    pub max_concurrent_tasks: i32,
    pub current_task_count: i32,
    pub status: String,
    pub last_heartbeat_at: DateTime<Utc>,
    pub connected_at: DateTime<Utc>,
    pub disconnected_at: Option<DateTime<Utc>>,
}

impl Daemon {
    /// Parse status string to DaemonStatus enum
    pub fn status_enum(&self) -> Result<DaemonStatus, String> {
        self.status.parse()
    }
}

/// Response for daemon list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonListResponse {
    pub daemons: Vec<Daemon>,
    pub total: i64,
}

/// Response for daemon directories endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonDirectoriesResponse {
    /// List of suggested directories from connected daemons
    pub directories: Vec<DaemonDirectory>,
}

/// A suggested directory from a daemon
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonDirectory {
    /// Path to the directory
    pub path: String,
    /// Display label for the directory
    pub label: String,
    /// Type of directory: "working", "makima", "worktrees"
    pub directory_type: String,
    /// Daemon hostname this directory is from
    pub hostname: Option<String>,
    /// Whether the directory already exists (for validation)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub exists: Option<bool>,
}

// =============================================================================
// Task Event Types
// =============================================================================

/// Task event record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskEvent {
    pub id: Uuid,
    pub task_id: Uuid,
    pub event_type: String,
    pub previous_status: Option<String>,
    pub new_status: Option<String>,
    #[sqlx(json)]
    pub event_data: Option<serde_json::Value>,
    pub created_at: DateTime<Utc>,
}

/// Response for task events list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskEventListResponse {
    pub events: Vec<TaskEvent>,
    pub total: i64,
}

/// A single output entry from a Claude Code task
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskOutputEntry {
    pub id: Uuid,
    pub task_id: Uuid,
    /// Message type: "assistant", "tool_use", "tool_result", "result", "system", "error", "raw"
    pub message_type: String,
    /// Main text content
    pub content: String,
    /// Tool name if tool_use message
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,
    /// Tool input JSON if tool_use message
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_input: Option<serde_json::Value>,
    /// Whether tool result was an error
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_error: Option<bool>,
    /// Cost in USD if result message
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cost_usd: Option<f64>,
    /// Duration in ms if result message
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,
    /// Timestamp when this output was recorded
    pub created_at: DateTime<Utc>,
}

impl TaskOutputEntry {
    /// Convert a TaskEvent with event_type='output' to a TaskOutputEntry
    pub fn from_task_event(event: TaskEvent) -> Option<Self> {
        if event.event_type != "output" {
            return None;
        }
        let data = event.event_data?;
        Some(Self {
            id: event.id,
            task_id: event.task_id,
            message_type: data.get("messageType")?.as_str()?.to_string(),
            content: data.get("content")?.as_str().unwrap_or("").to_string(),
            tool_name: data.get("toolName").and_then(|v| v.as_str()).map(|s| s.to_string()),
            tool_input: data.get("toolInput").cloned(),
            is_error: data.get("isError").and_then(|v| v.as_bool()),
            cost_usd: data.get("costUsd").and_then(|v| v.as_f64()),
            duration_ms: data.get("durationMs").and_then(|v| v.as_u64()),
            created_at: event.created_at,
        })
    }
}

/// Response for task output history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskOutputResponse {
    pub entries: Vec<TaskOutputEntry>,
    pub total: usize,
    pub task_id: Uuid,
}

// =============================================================================
// Mesh Chat History Types
// =============================================================================

/// Mesh chat conversation for persisting history
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MeshChatConversation {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub name: Option<String>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Individual message in a mesh chat conversation
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MeshChatMessageRecord {
    pub id: Uuid,
    pub conversation_id: Uuid,
    pub role: String,
    pub content: String,
    pub context_type: String,
    pub context_task_id: Option<Uuid>,
    /// Tool calls made during this message (JSON, nullable)
    pub tool_calls: Option<serde_json::Value>,
    /// Pending questions requiring user response (JSON, nullable)
    pub pending_questions: Option<serde_json::Value>,
    pub created_at: DateTime<Utc>,
}

/// Response for chat history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MeshChatHistoryResponse {
    pub conversation_id: Uuid,
    pub messages: Vec<MeshChatMessageRecord>,
}

// =============================================================================
// Contract Chat History Types
// =============================================================================

/// Conversation thread for contract chat (scoped to a specific contract)
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractChatConversation {
    pub id: Uuid,
    pub contract_id: Uuid,
    pub owner_id: Uuid,
    pub name: Option<String>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Individual message in a contract chat conversation
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractChatMessageRecord {
    pub id: Uuid,
    pub conversation_id: Uuid,
    pub role: String,
    pub content: String,
    /// Tool calls made during this message (JSON, nullable)
    pub tool_calls: Option<serde_json::Value>,
    /// Pending questions requiring user response (JSON, nullable)
    pub pending_questions: Option<serde_json::Value>,
    pub created_at: DateTime<Utc>,
}

/// Response for contract chat history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractChatHistoryResponse {
    pub contract_id: Uuid,
    pub conversation_id: Uuid,
    pub messages: Vec<ContractChatMessageRecord>,
}

// =============================================================================
// Merge API Types
// =============================================================================

/// Information about a task branch
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchInfo {
    /// Full branch name
    pub name: String,
    /// Task ID extracted from branch name (if parseable)
    pub task_id: Option<Uuid>,
    /// Whether this branch has been merged
    pub is_merged: bool,
    /// Short SHA of the last commit
    pub last_commit: String,
    /// Subject line of the last commit
    pub last_commit_message: String,
}

/// Response for branch list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct BranchListResponse {
    pub branches: Vec<BranchInfo>,
}

/// Request to start a merge
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeStartRequest {
    /// Branch name to merge
    pub source_branch: String,
}

/// Current merge state
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeStatusResponse {
    /// Whether a merge is in progress
    pub in_progress: bool,
    /// Branch being merged (if in progress)
    pub source_branch: Option<String>,
    /// Files with unresolved conflicts
    pub conflicted_files: Vec<String>,
}

/// Request to resolve a conflict
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeResolveRequest {
    /// File path to resolve
    pub file: String,
    /// Resolution strategy: "ours" or "theirs"
    pub strategy: String,
}

/// Request to commit a merge
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeCommitRequest {
    /// Commit message
    pub message: String,
}

/// Request to skip a subtask branch
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeSkipRequest {
    /// Subtask ID to skip
    pub subtask_id: Uuid,
    /// Reason for skipping
    pub reason: String,
}

/// Result of a merge operation
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeResultResponse {
    /// Whether the operation succeeded
    pub success: bool,
    /// Human-readable message
    pub message: String,
    /// Commit SHA (if a commit was created)
    pub commit_sha: Option<String>,
    /// Conflicted files (if conflicts occurred)
    pub conflicts: Option<Vec<String>>,
}

/// Response to check if all branches are merged
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct MergeCompleteCheckResponse {
    /// Whether the orchestrator can mark itself as complete
    pub can_complete: bool,
    /// Branches not yet merged or skipped
    pub unmerged_branches: Vec<String>,
    /// Count of merged branches
    pub merged_count: u32,
    /// Count of skipped branches
    pub skipped_count: u32,
}

// =============================================================================
// Contract Type Templates (User-defined)
// =============================================================================

/// A phase definition within a contract template
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseDefinition {
    /// Phase identifier (e.g., "research", "plan", "execute")
    pub id: String,
    /// Display name for the phase
    pub name: String,
    /// Order in the workflow (0-indexed)
    pub order: i32,
}

/// A deliverable definition within a phase
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DeliverableDefinition {
    /// Deliverable identifier (e.g., "plan-document", "pull-request")
    pub id: String,
    /// Display name for the deliverable
    pub name: String,
    /// Priority: "required", "recommended", or "optional"
    #[serde(default = "default_priority")]
    pub priority: String,
}

fn default_priority() -> String {
    "required".to_string()
}

/// Phase configuration stored on a contract (copied from template at creation)
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseConfig {
    /// Ordered list of phases in the workflow
    pub phases: Vec<PhaseDefinition>,
    /// Default starting phase
    pub default_phase: String,
    /// Deliverables per phase: { "phase_id": [deliverables] }
    #[serde(default)]
    pub deliverables: std::collections::HashMap<String, Vec<DeliverableDefinition>>,
}

/// Contract type template record from the database
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractTypeTemplateRecord {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    #[sqlx(json)]
    pub phases: Vec<PhaseDefinition>,
    pub default_phase: String,
    #[sqlx(json)]
    pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>,
    pub version: i32,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Request to create a new contract type template
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateTemplateRequest {
    pub name: String,
    pub description: Option<String>,
    pub phases: Vec<PhaseDefinition>,
    pub default_phase: String,
    pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>,
}

/// Request to update a contract type template
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTemplateRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub phases: Option<Vec<PhaseDefinition>>,
    pub default_phase: Option<String>,
    pub deliverables: Option<std::collections::HashMap<String, Vec<DeliverableDefinition>>>,
    /// Version for optimistic locking
    pub version: Option<i32>,
}

/// Summary of a contract type template for list views
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractTypeTemplateSummary {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub phases: Vec<PhaseDefinition>,
    pub default_phase: String,
    pub is_builtin: bool,
    pub version: i32,
    pub created_at: DateTime<Utc>,
}

// =============================================================================
// Contract Types
// =============================================================================

/// Contract type determines the workflow and required documents
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ContractType {
    /// Simple Plan -> Execute workflow (default)
    /// - Plan phase: requires a "Plan" document
    /// - Execute phase: requires a "PR" document
    Simple,
    /// Specification-based development with TDD
    /// - Research: requires "Research Notes" document
    /// - Specify: requires "Requirements Document"
    /// - Plan: requires "Plan" document
    /// - Execute: requires "PR" document
    /// - Review: requires "Release Notes" document
    Specification,
    /// Execute-only workflow with no deliverables
    /// - Only has "execute" phase
    /// - NO deliverables at all - just execute tasks directly
    Execute,
}

impl Default for ContractType {
    fn default() -> Self {
        ContractType::Simple
    }
}

impl std::fmt::Display for ContractType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ContractType::Simple => write!(f, "simple"),
            ContractType::Specification => write!(f, "specification"),
            ContractType::Execute => write!(f, "execute"),
        }
    }
}

impl std::str::FromStr for ContractType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "simple" => Ok(ContractType::Simple),
            "specification" => Ok(ContractType::Specification),
            "execute" => Ok(ContractType::Execute),
            _ => Err(format!("Unknown contract type: {}", s)),
        }
    }
}

/// Contract phase for workflow progression
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ContractPhase {
    Research,
    Specify,
    Plan,
    Execute,
    Review,
}

impl std::fmt::Display for ContractPhase {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ContractPhase::Research => write!(f, "research"),
            ContractPhase::Specify => write!(f, "specify"),
            ContractPhase::Plan => write!(f, "plan"),
            ContractPhase::Execute => write!(f, "execute"),
            ContractPhase::Review => write!(f, "review"),
        }
    }
}

impl std::str::FromStr for ContractPhase {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "research" => Ok(ContractPhase::Research),
            "specify" => Ok(ContractPhase::Specify),
            "plan" => Ok(ContractPhase::Plan),
            "execute" => Ok(ContractPhase::Execute),
            "review" => Ok(ContractPhase::Review),
            _ => Err(format!("Unknown contract phase: {}", s)),
        }
    }
}

/// Contract status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ContractStatus {
    Active,
    Completed,
    Archived,
}

impl std::fmt::Display for ContractStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ContractStatus::Active => write!(f, "active"),
            ContractStatus::Completed => write!(f, "completed"),
            ContractStatus::Archived => write!(f, "archived"),
        }
    }
}

impl std::str::FromStr for ContractStatus {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "active" => Ok(ContractStatus::Active),
            "completed" => Ok(ContractStatus::Completed),
            "archived" => Ok(ContractStatus::Archived),
            _ => Err(format!("Unknown contract status: {}", s)),
        }
    }
}

/// Repository source type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum RepositorySourceType {
    /// Existing remote repo (GitHub, GitLab, etc)
    Remote,
    /// Existing local repo
    Local,
    /// New repo created/managed by Makima daemon
    Managed,
}

impl std::fmt::Display for RepositorySourceType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RepositorySourceType::Remote => write!(f, "remote"),
            RepositorySourceType::Local => write!(f, "local"),
            RepositorySourceType::Managed => write!(f, "managed"),
        }
    }
}

impl std::str::FromStr for RepositorySourceType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "remote" => Ok(RepositorySourceType::Remote),
            "local" => Ok(RepositorySourceType::Local),
            "managed" => Ok(RepositorySourceType::Managed),
            _ => Err(format!("Unknown repository source type: {}", s)),
        }
    }
}

/// Repository status (for managed repos)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum RepositoryStatus {
    /// Repo is usable
    Ready,
    /// Waiting for daemon to create
    Pending,
    /// Daemon is creating the repo
    Creating,
    /// Creation failed
    Failed,
}

impl std::fmt::Display for RepositoryStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RepositoryStatus::Ready => write!(f, "ready"),
            RepositoryStatus::Pending => write!(f, "pending"),
            RepositoryStatus::Creating => write!(f, "creating"),
            RepositoryStatus::Failed => write!(f, "failed"),
        }
    }
}

impl std::str::FromStr for RepositoryStatus {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "ready" => Ok(RepositoryStatus::Ready),
            "pending" => Ok(RepositoryStatus::Pending),
            "creating" => Ok(RepositoryStatus::Creating),
            "failed" => Ok(RepositoryStatus::Failed),
            _ => Err(format!("Unknown repository status: {}", s)),
        }
    }
}

/// Contract record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Contract {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    /// Contract type: "simple" or "specification"
    pub contract_type: String,
    pub phase: String,
    pub status: String,
    /// The long-running supervisor task that orchestrates this contract
    #[serde(skip_serializing_if = "Option::is_none")]
    pub supervisor_task_id: Option<Uuid>,
    /// Whether tasks for this contract should run in autonomous loop mode.
    /// When enabled, tasks will automatically restart with --continue if they exit
    /// without a COMPLETION_GATE indicating ready: true.
    #[serde(default)]
    pub autonomous_loop: bool,
    /// Whether to wait for user confirmation before progressing to the next phase.
    /// When enabled, the supervisor will pause and ask the user to review and approve
    /// phase outputs (like plans, requirements, etc.) before continuing.
    #[serde(default)]
    pub phase_guard: bool,
    /// Completed deliverables per phase.
    /// Structure: { "plan": ["plan-document"], "execute": ["pull-request"] }
    #[sqlx(json)]
    #[serde(default)]
    pub completed_deliverables: serde_json::Value,
    /// Whether this contract operates in local-only mode.
    /// When enabled, automatic completion actions (branch, merge, pr) are skipped,
    /// allowing users to manually handle code changes via patch files or other means.
    #[serde(default)]
    pub local_only: bool,
    /// Whether to auto-merge to target branch locally when local_only mode is enabled.
    /// When both local_only and auto_merge_local are true, completed task changes will be
    /// automatically merged to the master/main branch locally (without pushing or creating PRs).
    #[serde(default)]
    pub auto_merge_local: bool,
    /// Phase configuration copied from template at contract creation (raw JSON).
    /// When present, this overrides the built-in contract type phases.
    /// Use `get_phase_config()` to get the parsed PhaseConfig.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phase_config: Option<serde_json::Value>,
    pub version: i32,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl Contract {
    /// Parse contract_type string to ContractType enum
    pub fn contract_type_enum(&self) -> Result<ContractType, String> {
        self.contract_type.parse()
    }

    /// Parse phase string to ContractPhase enum
    pub fn phase_enum(&self) -> Result<ContractPhase, String> {
        self.phase.parse()
    }

    /// Parse status string to ContractStatus enum
    pub fn status_enum(&self) -> Result<ContractStatus, String> {
        self.status.parse()
    }

    /// Get valid phase IDs for this contract (as strings)
    pub fn valid_phase_ids(&self) -> Vec<String> {
        // Check phase_config first (for custom templates)
        if let Some(config) = self.get_phase_config() {
            let mut phases: Vec<_> = config.phases.iter().collect();
            phases.sort_by_key(|p| p.order);
            return phases.iter().map(|p| p.id.clone()).collect();
        }

        // Fall back to built-in contract types
        match self.contract_type.as_str() {
            "simple" => vec!["plan".to_string(), "execute".to_string()],
            "specification" => vec![
                "research".to_string(),
                "specify".to_string(),
                "plan".to_string(),
                "execute".to_string(),
                "review".to_string(),
            ],
            "execute" => vec!["execute".to_string()],
            _ => vec!["plan".to_string(), "execute".to_string()],
        }
    }

    /// Get valid phases for this contract type (as ContractPhase enums)
    /// Note: For custom templates with non-standard phases, this only returns
    /// phases that map to the ContractPhase enum.
    pub fn valid_phases(&self) -> Vec<ContractPhase> {
        self.valid_phase_ids()
            .iter()
            .filter_map(|id| id.parse::<ContractPhase>().ok())
            .collect()
    }

    /// Get the initial phase ID for this contract type (as string)
    pub fn initial_phase_id(&self) -> String {
        // Check phase_config first (for custom templates)
        if let Some(config) = self.get_phase_config() {
            return config.default_phase.clone();
        }

        // Fall back to built-in contract types
        match self.contract_type.as_str() {
            "specification" => "research".to_string(),
            "execute" => "execute".to_string(),
            _ => "plan".to_string(),
        }
    }

    /// Get the initial phase for this contract type (as ContractPhase enum)
    pub fn initial_phase(&self) -> ContractPhase {
        self.initial_phase_id()
            .parse()
            .unwrap_or(ContractPhase::Plan)
    }

    /// Get the terminal phase ID for this contract type (as string)
    pub fn terminal_phase_id(&self) -> String {
        // Check phase_config first (for custom templates)
        if let Some(config) = self.get_phase_config() {
            // Last phase in sorted order is the terminal phase
            let mut phases: Vec<_> = config.phases.iter().collect();
            phases.sort_by_key(|p| p.order);
            if let Some(last) = phases.last() {
                return last.id.clone();
            }
        }

        // Fall back to built-in contract types
        match self.contract_type.as_str() {
            "specification" => "review".to_string(),
            _ => "execute".to_string(),
        }
    }

    /// Get the terminal phase for this contract type (phase where contract can be completed)
    pub fn terminal_phase(&self) -> ContractPhase {
        self.terminal_phase_id()
            .parse()
            .unwrap_or(ContractPhase::Execute)
    }

    /// Check if a phase ID is valid for this contract
    pub fn is_valid_phase(&self, phase_id: &str) -> bool {
        self.valid_phase_ids().contains(&phase_id.to_string())
    }

    /// Get the phase configuration for custom templates
    pub fn get_phase_config(&self) -> Option<PhaseConfig> {
        self.phase_config
            .as_ref()
            .and_then(|v| serde_json::from_value(v.clone()).ok())
    }

    /// Get completed deliverable IDs for a specific phase
    pub fn get_completed_deliverables(&self, phase: &str) -> Vec<String> {
        self.completed_deliverables
            .get(phase)
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(String::from))
                    .collect()
            })
            .unwrap_or_default()
    }

    /// Check if a specific deliverable is marked as complete for a phase
    pub fn is_deliverable_complete(&self, phase: &str, deliverable_id: &str) -> bool {
        self.get_completed_deliverables(phase)
            .contains(&deliverable_id.to_string())
    }
}

/// Contract repository record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractRepository {
    pub id: Uuid,
    pub contract_id: Uuid,
    pub name: String,
    pub repository_url: Option<String>,
    pub local_path: Option<String>,
    pub source_type: String,
    pub status: String,
    pub is_primary: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl ContractRepository {
    /// Parse source_type string to RepositorySourceType enum
    pub fn source_type_enum(&self) -> Result<RepositorySourceType, String> {
        self.source_type.parse()
    }

    /// Parse status string to RepositoryStatus enum
    pub fn status_enum(&self) -> Result<RepositoryStatus, String> {
        self.status.parse()
    }
}

/// Summary of a contract for list views
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractSummary {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    /// Contract type: "simple" or "specification"
    pub contract_type: String,
    pub phase: String,
    pub status: String,
    /// Supervisor task ID for contract orchestration
    pub supervisor_task_id: Option<Uuid>,
    /// When true, tasks do not auto-execute completion actions and work stays in worktrees.
    #[serde(default)]
    pub local_only: bool,
    /// When true with local_only, automatically merge completed tasks to target branch locally.
    #[serde(default)]
    pub auto_merge_local: bool,
    pub file_count: i64,
    pub task_count: i64,
    pub repository_count: i64,
    pub version: i32,
    pub created_at: DateTime<Utc>,
}

/// Contract with all relations for detail view
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractWithRelations {
    #[serde(flatten)]
    pub contract: Contract,
    pub repositories: Vec<ContractRepository>,
    pub files: Vec<FileSummary>,
    pub tasks: Vec<TaskSummary>,
}

/// Response for contract list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractListResponse {
    pub contracts: Vec<ContractSummary>,
    pub total: i64,
}

/// Request payload for creating a new contract
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateContractRequest {
    /// Name of the contract
    pub name: String,
    /// Optional description
    pub description: Option<String>,
    /// Contract type: "simple" (default), "specification", "execute", or a custom template name.
    /// For built-in types:
    /// - simple: Plan -> Execute workflow
    /// - specification: Research -> Specify -> Plan -> Execute -> Review
    /// - execute: Execute only
    /// For custom templates, use the template name or provide template_id.
    #[serde(default)]
    pub contract_type: Option<String>,
    /// UUID of a custom template to use. If provided, this takes precedence over contract_type.
    /// The template's phase configuration will be copied to the contract.
    #[serde(default)]
    pub template_id: Option<Uuid>,
    /// Initial phase to start in (defaults based on contract_type or template)
    /// - simple: defaults to "plan"
    /// - specification: defaults to "research"
    #[serde(default)]
    pub initial_phase: Option<String>,
    /// Enable autonomous loop mode for tasks in this contract.
    /// When enabled, tasks automatically restart with --continue if they exit
    /// without a COMPLETION_GATE indicating ready: true.
    #[serde(default)]
    pub autonomous_loop: Option<bool>,
    /// Enable phase guard mode for this contract.
    /// When enabled, the supervisor will pause and ask the user to review and approve
    /// phase outputs before progressing to the next phase.
    #[serde(default)]
    pub phase_guard: Option<bool>,
    /// Enable local-only mode for this contract.
    /// When enabled, automatic completion actions (branch, merge, pr) are skipped,
    /// allowing users to manually handle code changes via patch files or other means.
    #[serde(default)]
    pub local_only: Option<bool>,
    /// Enable auto-merge to target branch locally when local_only mode is enabled.
    /// When both local_only and auto_merge_local are true, completed task changes will be
    /// automatically merged to the master/main branch locally (without pushing or creating PRs).
    #[serde(default)]
    pub auto_merge_local: Option<bool>,
}

/// Request payload for updating a contract
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateContractRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub phase: Option<String>,
    pub status: Option<String>,
    /// Supervisor task ID for contract orchestration
    #[serde(skip_serializing_if = "Option::is_none")]
    pub supervisor_task_id: Option<Uuid>,
    /// Enable or disable autonomous loop mode for tasks in this contract.
    #[serde(default)]
    pub autonomous_loop: Option<bool>,
    /// Enable or disable phase guard mode for this contract.
    /// When enabled, the supervisor will pause and ask the user to review and approve
    /// phase outputs before progressing to the next phase.
    #[serde(default)]
    pub phase_guard: Option<bool>,
    /// Enable or disable local-only mode for this contract.
    /// When enabled, automatic completion actions (branch, merge, pr) are skipped,
    /// allowing users to manually handle code changes via patch files or other means.
    #[serde(default)]
    pub local_only: Option<bool>,
    /// Enable or disable auto-merge to target branch locally when local_only mode is enabled.
    /// When both local_only and auto_merge_local are true, completed task changes will be
    /// automatically merged to the master/main branch locally (without pushing or creating PRs).
    #[serde(default)]
    pub auto_merge_local: Option<bool>,
    /// Version for optimistic locking
    pub version: Option<i32>,
}

/// Request to add a remote repository to a contract
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct AddRemoteRepositoryRequest {
    pub name: String,
    pub repository_url: String,
    #[serde(default)]
    pub is_primary: bool,
}

/// Request to add a local repository to a contract
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct AddLocalRepositoryRequest {
    pub name: String,
    pub local_path: String,
    #[serde(default)]
    pub is_primary: bool,
}

/// Request to create a managed repository (daemon will create it)
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateManagedRepositoryRequest {
    pub name: String,
    #[serde(default)]
    pub is_primary: bool,
}

/// Request to change contract phase
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ChangePhaseRequest {
    pub phase: String,
    /// If phase_guard is enabled, this must be true to confirm the transition.
    /// If not provided or false, returns phase deliverables for review.
    #[serde(default)]
    pub confirmed: Option<bool>,
    /// User feedback for changes (used when not confirming)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub feedback: Option<String>,
    /// Expected version for optimistic locking. If provided, the phase change
    /// will only succeed if the current contract version matches.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expected_version: Option<i32>,
}

/// Result of a phase change operation, supporting explicit conflict detection.
#[derive(Debug, Clone)]
pub enum PhaseChangeResult {
    /// Phase change succeeded, returning the updated contract
    Success(Contract),
    /// Version conflict: the contract was modified concurrently
    VersionConflict {
        /// The version the client expected
        expected: i32,
        /// The actual current version in the database
        actual: i32,
        /// The current phase of the contract
        current_phase: String,
    },
    /// Validation failed (e.g., invalid phase transition)
    ValidationFailed {
        /// Human-readable reason for the failure
        reason: String,
        /// List of missing requirements for the phase transition
        missing_requirements: Vec<String>,
    },
    /// The caller is not authorized to change this contract's phase
    Unauthorized,
    /// The contract was not found
    NotFound,
}

/// Response for phase transition when phase_guard is enabled
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseTransitionRequest {
    /// Current contract phase
    pub current_phase: String,
    /// Requested next phase
    pub next_phase: String,
    /// Summary of phase deliverables/outputs
    pub deliverables_summary: String,
    /// List of files created in this phase
    pub phase_files: Vec<PhaseFileInfo>,
    /// List of completed tasks in this phase
    pub phase_tasks: Vec<PhaseTaskInfo>,
    /// Whether user confirmation is required
    pub requires_confirmation: bool,
    /// Unique ID for tracking this transition request
    pub transition_id: String,
}

/// File info for phase transition review
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseFileInfo {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
}

/// Task info for phase transition review
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PhaseTaskInfo {
    pub id: Uuid,
    pub name: String,
    pub status: String,
}

/// Contract event record from the database
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractEvent {
    pub id: Uuid,
    pub contract_id: Uuid,
    pub event_type: String,
    pub previous_phase: Option<String>,
    pub new_phase: Option<String>,
    #[sqlx(json)]
    pub event_data: Option<serde_json::Value>,
    pub created_at: DateTime<Utc>,
}

/// Response for contract events list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractEventListResponse {
    pub events: Vec<ContractEvent>,
    pub total: i64,
}

// ============================================================================
// Task Checkpoints (for git checkpoint tracking)
// ============================================================================

/// Task checkpoint record - represents a git commit checkpoint
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskCheckpoint {
    pub id: Uuid,
    pub task_id: Uuid,
    /// Sequential checkpoint number within this task
    pub checkpoint_number: i32,
    /// Git commit SHA
    pub commit_sha: String,
    /// Git branch name
    pub branch_name: String,
    /// Commit message
    pub message: String,
    /// Files changed in this commit: [{path, action: 'A'|'M'|'D'}]
    #[sqlx(json)]
    pub files_changed: Option<serde_json::Value>,
    /// Lines added in this commit
    pub lines_added: Option<i32>,
    /// Lines removed in this commit
    pub lines_removed: Option<i32>,
    pub created_at: DateTime<Utc>,
}

/// Request to create a checkpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateCheckpointRequest {
    /// Commit message
    pub message: String,
}

/// Response for checkpoint list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointListResponse {
    pub checkpoints: Vec<TaskCheckpoint>,
    pub total: i64,
}

// ============================================================================
// Supervisor State (for supervisor resumability)
// ============================================================================

/// Supervisor state for contract supervisor tasks
/// Enables resumption after interruption
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorState {
    pub id: Uuid,
    pub contract_id: Uuid,
    pub task_id: Uuid,
    /// Full Claude conversation history for resumption
    #[sqlx(json)]
    pub conversation_history: serde_json::Value,
    /// Last checkpoint this supervisor created
    pub last_checkpoint_id: Option<Uuid>,
    /// Tasks the supervisor is waiting on
    #[sqlx(try_from = "Vec<Uuid>")]
    pub pending_task_ids: Vec<Uuid>,
    /// Current contract phase when supervisor was last active
    pub phase: String,
    /// When supervisor was last active
    pub last_activity: DateTime<Utc>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    /// Current supervisor state (initializing, idle, working, waiting_for_user, etc.)
    pub state: String,
    /// Human-readable description of current activity
    pub current_activity: Option<String>,
    /// Progress percentage (0-100)
    pub progress: i32,
    /// Error message when state is failed or blocked
    pub error_message: Option<String>,
    /// Tasks spawned by this supervisor
    #[sqlx(try_from = "Vec<Uuid>")]
    pub spawned_task_ids: Vec<Uuid>,
    /// Pending questions awaiting user response
    #[sqlx(json)]
    pub pending_questions: serde_json::Value,
    /// Number of times this supervisor has been restored
    pub restoration_count: i32,
    /// Timestamp of last restoration
    pub last_restored_at: Option<DateTime<Utc>>,
    /// Source of last restoration (daemon_restart, task_reassignment, manual)
    pub restoration_source: Option<String>,
}

/// Pending question structure for supervisor state
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PendingQuestion {
    /// Unique question ID
    pub id: Uuid,
    /// The question text
    pub question: String,
    /// Optional choices (empty for free-form)
    #[serde(default)]
    pub choices: Vec<String>,
    /// Optional context
    pub context: Option<String>,
    /// Question type: general, phase_confirmation, contract_complete
    #[serde(default = "default_question_type")]
    pub question_type: String,
    /// When the question was asked
    pub asked_at: DateTime<Utc>,
}

fn default_question_type() -> String {
    "general".to_string()
}

/// Request to update supervisor state
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSupervisorStateRequest {
    /// Updated conversation history
    pub conversation_history: Option<serde_json::Value>,
    /// Tasks the supervisor is waiting on
    pub pending_task_ids: Option<Vec<Uuid>>,
    /// Current contract phase
    pub phase: Option<String>,
    /// Current supervisor state
    pub state: Option<String>,
    /// Current activity description
    pub current_activity: Option<String>,
    /// Progress percentage
    pub progress: Option<i32>,
    /// Error message
    pub error_message: Option<String>,
    /// Spawned task IDs
    pub spawned_task_ids: Option<Vec<Uuid>>,
    /// Pending questions
    pub pending_questions: Option<serde_json::Value>,
}

/// Restoration context returned when restoring a supervisor
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorRestorationContext {
    /// Whether restoration was successful
    pub success: bool,
    /// Previous state before restoration
    pub previous_state: SupervisorStateEnum,
    /// Restored conversation history
    pub conversation_history: serde_json::Value,
    /// Pending questions that need re-delivery
    pub pending_questions: Vec<PendingQuestion>,
    /// Tasks still being waited on
    pub waiting_task_ids: Vec<Uuid>,
    /// Spawned tasks to check status of
    pub spawned_task_ids: Vec<Uuid>,
    /// Restoration count (incremented)
    pub restoration_count: i32,
    /// Context message for Claude
    pub restoration_context_message: String,
    /// Any warnings during restoration
    pub warnings: Vec<String>,
}

/// Validation result for supervisor state consistency
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateValidationResult {
    pub is_valid: bool,
    pub issues: Vec<String>,
    /// Suggested recovery action
    pub recovery_action: StateRecoveryAction,
}

/// Action to take when state validation fails
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StateRecoveryAction {
    /// State is valid, proceed with restoration
    Proceed,
    /// Start from last checkpoint
    UseCheckpoint,
    /// Start fresh
    StartFresh,
    /// Manual intervention required
    ManualIntervention,
}

// ============================================================================
// Daemon Task Assignments (for multi-daemon support)
// ============================================================================

/// Daemon task assignment record
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonTaskAssignment {
    pub id: Uuid,
    pub daemon_id: Uuid,
    pub task_id: Uuid,
    pub assigned_at: DateTime<Utc>,
    /// Status: 'active', 'migrating', 'completed'
    pub status: String,
}

/// Extended daemon info for selection
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DaemonWithCapacity {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub connection_id: String,
    pub hostname: Option<String>,
    pub machine_id: Option<String>,
    pub max_concurrent_tasks: i32,
    pub current_task_count: i32,
    pub capacity_score: Option<i32>,
    pub task_queue_length: Option<i32>,
    pub supports_migration: Option<bool>,
    pub status: String,
    pub last_heartbeat_at: DateTime<Utc>,
    pub connected_at: DateTime<Utc>,
}

// ============================================================================
// Repository History (for storing and suggesting previously used repositories)
// ============================================================================

/// Repository history entry - tracks previously used repositories for suggestions
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RepositoryHistoryEntry {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub name: String,
    pub repository_url: Option<String>,
    pub local_path: Option<String>,
    pub source_type: String,
    pub use_count: i32,
    pub last_used_at: DateTime<Utc>,
    pub created_at: DateTime<Utc>,
}

/// Response for repository history list endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RepositoryHistoryListResponse {
    pub entries: Vec<RepositoryHistoryEntry>,
    pub total: i64,
}

/// Request for getting repository suggestions
#[derive(Debug, Deserialize, ToSchema)]
pub struct RepositorySuggestionsQuery {
    /// Filter by source type: 'remote' or 'local'
    pub source_type: Option<String>,
    /// Optional search query to filter by name or URL/path
    pub query: Option<String>,
    /// Limit results (default: 10)
    pub limit: Option<i32>,
}

// =============================================================================
// Resume and History System Types
// =============================================================================

/// Conversation snapshot for task resumption
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ConversationSnapshot {
    pub id: Uuid,
    pub task_id: Uuid,
    pub checkpoint_id: Option<Uuid>,
    /// Snapshot type: 'auto', 'manual', 'checkpoint'
    pub snapshot_type: String,
    pub message_count: i32,
    #[sqlx(json)]
    pub conversation_state: serde_json::Value,
    #[sqlx(json)]
    pub metadata: Option<serde_json::Value>,
    pub created_at: DateTime<Utc>,
}

/// History event for contract/task history tracking
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct HistoryEvent {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub contract_id: Option<Uuid>,
    pub task_id: Option<Uuid>,
    pub event_type: String,
    pub event_subtype: Option<String>,
    pub phase: Option<String>,
    #[sqlx(json)]
    pub event_data: serde_json::Value,
    pub created_at: DateTime<Utc>,
}

/// Unified conversation message for API responses
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ConversationMessage {
    pub id: String,
    /// Message role: 'user', 'assistant', 'system', 'tool'
    pub role: String,
    pub content: String,
    pub timestamp: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<Vec<ToolCallInfo>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_input: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_result: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub is_error: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub token_count: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cost_usd: Option<f64>,
}

/// Tool call information within a conversation message
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ToolCallInfo {
    pub id: String,
    pub name: String,
    pub input: serde_json::Value,
}

/// Query filters for history endpoints
#[derive(Debug, Deserialize, ToSchema, Default)]
#[serde(rename_all = "camelCase")]
pub struct HistoryQueryFilters {
    pub phase: Option<String>,
    pub event_types: Option<Vec<String>>,
    #[serde(default, deserialize_with = "flexible_datetime::deserialize")]
    pub from: Option<DateTime<Utc>>,
    #[serde(default, deserialize_with = "flexible_datetime::deserialize")]
    pub to: Option<DateTime<Utc>>,
    pub limit: Option<i32>,
    pub cursor: Option<String>,
}

/// Request to resume a supervisor
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ResumeSupervisorRequest {
    pub target_daemon_id: Option<Uuid>,
    /// Resume mode: 'continue', 'restart_phase', 'from_checkpoint'
    pub resume_mode: String,
    pub checkpoint_id: Option<Uuid>,
    pub additional_context: Option<String>,
}

/// Request to resume from a checkpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ResumeFromCheckpointRequest {
    pub task_name: Option<String>,
    pub plan: String,
    pub include_conversation: Option<bool>,
    pub target_daemon_id: Option<Uuid>,
}

/// Request to rewind a task to a checkpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewindTaskRequest {
    pub checkpoint_id: Option<Uuid>,
    pub checkpoint_sha: Option<String>,
    /// Preserve mode: 'discard', 'create_branch', 'stash'
    pub preserve_mode: String,
    pub branch_name: Option<String>,
}

/// Request to rewind a conversation
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewindConversationRequest {
    pub to_message_id: Option<String>,
    pub to_timestamp: Option<DateTime<Utc>>,
    pub by_message_count: Option<i32>,
    pub rewind_code: Option<bool>,
}

/// Request to fork a task
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ForkTaskRequest {
    /// Fork from type: 'checkpoint', 'timestamp', 'message_id'
    pub fork_from_type: String,
    pub fork_from_value: String,
    pub new_task_name: String,
    pub new_task_plan: String,
    pub include_conversation: Option<bool>,
    pub create_branch: Option<bool>,
    pub branch_name: Option<String>,
}

/// Response for contract history endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ContractHistoryResponse {
    pub contract_id: Uuid,
    pub entries: Vec<HistoryEvent>,
    pub total_count: i64,
    pub cursor: Option<String>,
}

/// Response for task conversation endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskConversationResponse {
    pub task_id: Uuid,
    pub task_name: String,
    pub status: String,
    pub messages: Vec<ConversationMessage>,
    pub total_tokens: Option<i32>,
    pub total_cost: Option<f64>,
}

/// Response for supervisor conversation endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorConversationResponse {
    pub contract_id: Uuid,
    pub supervisor_task_id: Uuid,
    pub phase: String,
    pub last_activity: DateTime<Utc>,
    pub pending_task_ids: Vec<Uuid>,
    pub messages: Vec<ConversationMessage>,
    pub spawned_tasks: Vec<TaskReference>,
}

/// Reference to a task for history/conversation responses
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct TaskReference {
    pub task_id: Uuid,
    pub task_name: String,
    pub status: String,
    pub created_at: DateTime<Utc>,
    pub completed_at: Option<DateTime<Utc>>,
}

/// Response for task rewind operation
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RewindTaskResponse {
    pub task_id: Uuid,
    pub rewinded_to: CheckpointInfo,
    pub preserved_as: Option<PreservedState>,
}

/// Checkpoint information in rewind response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointInfo {
    pub checkpoint_number: i32,
    pub sha: String,
    pub message: String,
}

/// Preserved state information in rewind response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PreservedState {
    /// State type: 'branch' or 'stash'
    pub state_type: String,
    pub reference: String,
}

/// Response for task fork operation
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ForkTaskResponse {
    pub new_task_id: Uuid,
    pub source_task_id: Uuid,
    pub fork_point: ForkPoint,
    pub branch_name: Option<String>,
    pub conversation_included: bool,
    pub message_count: Option<i32>,
}

/// Fork point information in fork response
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ForkPoint {
    pub fork_type: String,
    pub checkpoint: Option<TaskCheckpoint>,
    pub timestamp: DateTime<Utc>,
}

// ============================================================================
// Checkpoint Patches (for task recovery when worktrees are lost)
// ============================================================================

/// A stored git patch for checkpoint recovery.
/// Enables task recovery when local worktrees are deleted or corrupted.
#[derive(Debug, Clone, FromRow, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointPatch {
    pub id: Uuid,
    pub task_id: Uuid,
    /// Optional link to a task_checkpoint record
    pub checkpoint_id: Option<Uuid>,
    /// The commit SHA that the patch should be applied on top of
    pub base_commit_sha: String,
    /// Compressed git diff data (gzip)
    #[sqlx(rename = "patch_data")]
    #[serde(skip)] // Don't serialize binary data to JSON
    pub patch_data: Vec<u8>,
    /// Size of the compressed patch in bytes
    pub patch_size_bytes: i32,
    /// Number of files affected by this patch
    pub files_count: i32,
    pub created_at: DateTime<Utc>,
    /// When this patch expires and will be automatically deleted
    pub expires_at: DateTime<Utc>,
}

/// Response for checkpoint patch (without binary data)
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CheckpointPatchInfo {
    pub id: Uuid,
    pub task_id: Uuid,
    pub checkpoint_id: Option<Uuid>,
    pub base_commit_sha: String,
    pub patch_size_bytes: i32,
    pub files_count: i32,
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
}

// ============================================================================
// Red Team Types
// ============================================================================

// =============================================================================
// Supervisor State and Heartbeat Types
// =============================================================================

/// Supervisor state for contract supervisor tasks.
/// Captures detailed activity state for monitoring and restoration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum SupervisorStateEnum {
    /// Supervisor is starting up
    Initializing,
    /// Supervisor is idle, waiting for work
    Idle,
    /// Supervisor is actively working
    Working,
    /// Supervisor is waiting for user input/confirmation
    WaitingForUser,
    /// Supervisor is waiting for spawned tasks to complete
    WaitingForTasks,
    /// Supervisor is blocked (external dependency, error, etc.)
    Blocked,
    /// Supervisor has completed its contract
    Completed,
    /// Supervisor has failed
    Failed,
    /// Supervisor was interrupted (daemon crash, etc.)
    Interrupted,
}

impl Default for SupervisorStateEnum {
    fn default() -> Self {
        SupervisorStateEnum::Initializing
    }
}

impl std::fmt::Display for SupervisorStateEnum {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SupervisorStateEnum::Initializing => write!(f, "initializing"),
            SupervisorStateEnum::Idle => write!(f, "idle"),
            SupervisorStateEnum::Working => write!(f, "working"),
            SupervisorStateEnum::WaitingForUser => write!(f, "waiting_for_user"),
            SupervisorStateEnum::WaitingForTasks => write!(f, "waiting_for_tasks"),
            SupervisorStateEnum::Blocked => write!(f, "blocked"),
            SupervisorStateEnum::Completed => write!(f, "completed"),
            SupervisorStateEnum::Failed => write!(f, "failed"),
            SupervisorStateEnum::Interrupted => write!(f, "interrupted"),
        }
    }
}

impl std::str::FromStr for SupervisorStateEnum {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "initializing" => Ok(SupervisorStateEnum::Initializing),
            "idle" => Ok(SupervisorStateEnum::Idle),
            "working" => Ok(SupervisorStateEnum::Working),
            "waiting_for_user" | "waitingforuser" => Ok(SupervisorStateEnum::WaitingForUser),
            "waiting_for_tasks" | "waitingfortasks" => Ok(SupervisorStateEnum::WaitingForTasks),
            "blocked" => Ok(SupervisorStateEnum::Blocked),
            "completed" => Ok(SupervisorStateEnum::Completed),
            "failed" => Ok(SupervisorStateEnum::Failed),
            "interrupted" => Ok(SupervisorStateEnum::Interrupted),
            _ => Err(format!("Unknown supervisor state: {}", s)),
        }
    }
}

/// Enhanced heartbeat record for supervisor task monitoring.
/// Stored in the database for historical analysis and dead supervisor detection.
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatRecord {
    pub id: Uuid,
    pub supervisor_task_id: Uuid,
    pub contract_id: Uuid,
    pub state: String,
    pub phase: String,
    pub current_activity: Option<String>,
    pub progress: i32,
    #[sqlx(try_from = "Vec<Uuid>")]
    pub pending_task_ids: Vec<Uuid>,
    pub timestamp: DateTime<Utc>,
}

/// Request payload for sending a supervisor heartbeat.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatRequest {
    pub task_id: Uuid,
    pub contract_id: Uuid,
    pub state: SupervisorStateEnum,
    pub phase: String,
    pub current_activity: Option<String>,
    /// Progress percentage (0-100)
    pub progress: u8,
    pub pending_task_ids: Vec<Uuid>,
}

// ============================================================================
// Supervisor Status API Types
// ============================================================================

/// Response for supervisor status endpoint
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorStatusResponse {
    /// The supervisor task ID
    pub task_id: Uuid,
    /// Current supervisor state (from supervisor_states table)
    pub state: String,
    /// Current contract phase
    pub phase: String,
    /// Description of current activity (from task progress_summary)
    pub current_activity: Option<String>,
    /// Progress percentage (0-100)
    pub progress: Option<u8>,
    /// When the supervisor last updated its state
    pub last_heartbeat: DateTime<Utc>,
    /// Task IDs the supervisor is currently waiting on
    pub pending_task_ids: Vec<Uuid>,
    /// Whether the supervisor is currently running
    pub is_running: bool,
}

/// Individual heartbeat entry for history
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatEntry {
    /// Timestamp of this heartbeat
    pub timestamp: DateTime<Utc>,
    /// Supervisor state at this time
    pub state: String,
    /// Activity description at this time
    pub activity: Option<String>,
    /// Progress at this time
    pub progress: Option<u8>,
    /// Contract phase at this time
    pub phase: String,
    /// Pending task IDs at this time
    pub pending_task_ids: Vec<Uuid>,
}

/// Response for supervisor heartbeat history endpoint
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorHeartbeatHistoryResponse {
    /// List of heartbeat entries
    pub heartbeats: Vec<SupervisorHeartbeatEntry>,
    /// Total count of heartbeats (for pagination)
    pub total: i64,
}

/// Response for supervisor sync endpoint
#[derive(Debug, Clone, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct SupervisorSyncResponse {
    /// Whether the sync was successful
    pub synced: bool,
    /// Current supervisor state after sync
    pub state: String,
    /// Optional message about the sync result
    pub message: Option<String>,
}

/// Query parameters for heartbeat history endpoint
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct HeartbeatHistoryQuery {
    /// Maximum number of heartbeats to return (default: 10)
    pub limit: Option<i32>,
    /// Offset for pagination (default: 0)
    pub offset: Option<i32>,
}

// =============================================================================
// Unit Tests
// =============================================================================

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

    #[test]
    fn test_supervisor_state_enum_display() {
        assert_eq!(SupervisorStateEnum::Initializing.to_string(), "initializing");
        assert_eq!(SupervisorStateEnum::Idle.to_string(), "idle");
        assert_eq!(SupervisorStateEnum::Working.to_string(), "working");
        assert_eq!(SupervisorStateEnum::WaitingForUser.to_string(), "waiting_for_user");
        assert_eq!(SupervisorStateEnum::WaitingForTasks.to_string(), "waiting_for_tasks");
        assert_eq!(SupervisorStateEnum::Blocked.to_string(), "blocked");
        assert_eq!(SupervisorStateEnum::Completed.to_string(), "completed");
        assert_eq!(SupervisorStateEnum::Failed.to_string(), "failed");
        assert_eq!(SupervisorStateEnum::Interrupted.to_string(), "interrupted");
    }

    #[test]
    fn test_supervisor_state_enum_from_str() {
        // Standard lowercase
        assert_eq!("initializing".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Initializing);
        assert_eq!("idle".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Idle);
        assert_eq!("working".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Working);
        assert_eq!("waiting_for_user".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForUser);
        assert_eq!("waiting_for_tasks".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForTasks);
        assert_eq!("blocked".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Blocked);
        assert_eq!("completed".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Completed);
        assert_eq!("failed".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Failed);
        assert_eq!("interrupted".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Interrupted);

        // Case insensitive
        assert_eq!("WORKING".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Working);
        assert_eq!("Working".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::Working);

        // Alternative formats
        assert_eq!("waitingforuser".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForUser);
        assert_eq!("waitingfortasks".parse::<SupervisorStateEnum>().unwrap(), SupervisorStateEnum::WaitingForTasks);

        // Invalid state
        assert!("invalid_state".parse::<SupervisorStateEnum>().is_err());
    }

    #[test]
    fn test_supervisor_state_enum_serialization() {
        // Test JSON serialization
        let state = SupervisorStateEnum::Working;
        let json = serde_json::to_string(&state).unwrap();
        assert_eq!(json, "\"working\"");

        // Test JSON deserialization
        let deserialized: SupervisorStateEnum = serde_json::from_str("\"working\"").unwrap();
        assert_eq!(deserialized, SupervisorStateEnum::Working);

        // Test underscore variants
        let json = "\"waiting_for_user\"";
        let deserialized: SupervisorStateEnum = serde_json::from_str(json).unwrap();
        assert_eq!(deserialized, SupervisorStateEnum::WaitingForUser);
    }

    #[test]
    fn test_supervisor_state_enum_default() {
        let default_state = SupervisorStateEnum::default();
        assert_eq!(default_state, SupervisorStateEnum::Initializing);
    }

    #[test]
    fn test_supervisor_heartbeat_request_serialization() {
        let request = SupervisorHeartbeatRequest {
            task_id: Uuid::nil(),
            contract_id: Uuid::nil(),
            state: SupervisorStateEnum::Working,
            phase: "execute".to_string(),
            current_activity: Some("Implementing feature".to_string()),
            progress: 50,
            pending_task_ids: vec![Uuid::nil()],
        };

        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("\"state\":\"working\""));
        assert!(json.contains("\"phase\":\"execute\""));
        assert!(json.contains("\"progress\":50"));
        assert!(json.contains("\"currentActivity\":\"Implementing feature\""));

        // Test deserialization
        let deserialized: SupervisorHeartbeatRequest = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.state, SupervisorStateEnum::Working);
        assert_eq!(deserialized.phase, "execute");
        assert_eq!(deserialized.progress, 50);
    }
}

// =============================================================================
// Directive Types
// =============================================================================

/// A directive — a long-lived top-level entity for managing projects via a DAG of steps.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Directive {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub title: String,
    pub goal: String,
    /// Status: draft, active, idle, paused, archived
    pub status: String,
    pub repository_url: Option<String>,
    pub local_path: Option<String>,
    pub base_branch: Option<String>,
    pub orchestrator_task_id: Option<Uuid>,
    pub pr_url: Option<String>,
    pub pr_branch: Option<String>,
    pub completion_task_id: Option<Uuid>,
    /// Question timeout mode: "auto" (30s timeout), "semi-auto" (block indefinitely), "manual" (block + ask many questions)
    pub reconcile_mode: String,
    pub goal_updated_at: DateTime<Utc>,
    pub started_at: Option<DateTime<Utc>>,
    pub version: i32,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// A historical record of a directive goal change.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveGoalHistory {
    pub id: Uuid,
    pub directive_id: Uuid,
    pub goal: String,
    pub created_at: DateTime<Utc>,
}

/// A step in a directive's DAG.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveStep {
    pub id: Uuid,
    pub directive_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub task_plan: Option<String>,
    pub depends_on: Vec<Uuid>,
    /// Status: pending, ready, running, completed, failed, skipped
    pub status: String,
    pub task_id: Option<Uuid>,
    /// Optional contract ID for contract-backed execution.
    pub contract_id: Option<Uuid>,
    /// Optional contract type (e.g. "simple", "specification", "execute").
    /// When set, the orchestrator creates a contract instead of a standalone task.
    pub contract_type: Option<String>,
    pub order_index: i32,
    pub generation: i32,
    pub started_at: Option<DateTime<Utc>>,
    pub completed_at: Option<DateTime<Utc>>,
    pub created_at: DateTime<Utc>,
}

/// Directive with its steps for detail view.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveWithSteps {
    #[serde(flatten)]
    pub directive: Directive,
    pub steps: Vec<DirectiveStep>,
}

/// Summary for directive list views.
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveSummary {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub title: String,
    pub goal: String,
    pub status: String,
    pub repository_url: Option<String>,
    pub orchestrator_task_id: Option<Uuid>,
    pub pr_url: Option<String>,
    pub completion_task_id: Option<Uuid>,
    /// Question timeout mode: "auto" (30s timeout), "semi-auto" (block indefinitely), "manual" (block + ask many questions)
    pub reconcile_mode: String,
    pub version: i32,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub total_steps: i64,
    pub completed_steps: i64,
    pub running_steps: i64,
    pub failed_steps: i64,
}

/// List response for directives.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveListResponse {
    pub directives: Vec<DirectiveSummary>,
    pub total: i64,
}

/// Request to create a new directive.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateDirectiveRequest {
    pub title: String,
    pub goal: String,
    pub repository_url: Option<String>,
    pub local_path: Option<String>,
    pub base_branch: Option<String>,
    /// Question timeout mode: "auto", "semi-auto", or "manual"
    pub reconcile_mode: Option<String>,
}

/// Request to update a directive.
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDirectiveRequest {
    pub title: Option<String>,
    pub goal: Option<String>,
    pub status: Option<String>,
    pub repository_url: Option<String>,
    pub local_path: Option<String>,
    pub base_branch: Option<String>,
    pub orchestrator_task_id: Option<Uuid>,
    pub pr_url: Option<String>,
    pub pr_branch: Option<String>,
    /// Question timeout mode: "auto", "semi-auto", or "manual"
    pub reconcile_mode: Option<String>,
    pub version: Option<i32>,
}

/// Request to update a directive's goal (triggers re-planning).
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateGoalRequest {
    pub goal: String,
}

/// Response for cleanup_directive_tasks (legacy).
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CleanupTasksResponse {
    pub deleted: i64,
}

/// Response for cleanup_directive endpoint.
#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CleanupResponse {
    pub message: String,
    pub task_id: Option<Uuid>,
}

/// Response for pick_up_orders endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct PickUpOrdersResponse {
    pub message: String,
    pub order_count: i64,
    pub task_id: Option<Uuid>,
}

/// Request to create a directive step.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateDirectiveStepRequest {
    pub name: String,
    pub description: Option<String>,
    pub task_plan: Option<String>,
    #[serde(default)]
    pub depends_on: Vec<Uuid>,
    #[serde(default)]
    pub order_index: i32,
    pub generation: Option<i32>,
    /// Optional order ID to auto-link this step to an order.
    #[serde(default)]
    pub order_id: Option<Uuid>,
    /// Optional: create a contract for this step instead of a standalone task.
    /// Valid values: "simple", "specification", "execute"
    #[serde(default)]
    pub contract_type: Option<String>,
}

/// Request to update a directive step.
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDirectiveStepRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub task_plan: Option<String>,
    pub depends_on: Option<Vec<Uuid>>,
    pub status: Option<String>,
    pub task_id: Option<Uuid>,
    pub order_index: Option<i32>,
}

// =============================================================================
// Order Types
// =============================================================================

/// An order — a card-based work item (feature, bug, spike, chore, improvement)
/// similar to GitHub Issues or Linear cards. Orders are linked to directives
/// for execution.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Order {
    pub id: Uuid,
    pub owner_id: Uuid,
    pub title: String,
    pub description: Option<String>,
    /// Priority: critical, high, medium, low, none
    pub priority: String,
    /// Status: open, in_progress, under_review, done, archived
    pub status: String,
    /// Type of work: feature, bug, spike, chore, improvement
    pub order_type: String,
    /// Flexible labels as JSON array of strings
    pub labels: serde_json::Value,
    /// Linked directive (required for new orders, nullable for legacy rows)
    pub directive_id: Option<Uuid>,
    /// Linked directive step (optional)
    pub directive_step_id: Option<Uuid>,
    /// Denormalized directive name for searchability (auto-populated by DB trigger)
    pub directive_name: Option<String>,
    /// Repository context
    pub repository_url: Option<String>,
    /// Optional DOG (Directive Order Group) this order belongs to
    pub dog_id: Option<Uuid>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Request to create a new order.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateOrderRequest {
    pub title: String,
    pub description: Option<String>,
    pub priority: Option<String>,
    pub status: Option<String>,
    pub order_type: Option<String>,
    #[serde(default = "default_empty_labels")]
    pub labels: serde_json::Value,
    /// Directive ID is required for new orders.
    pub directive_id: Uuid,
    pub repository_url: Option<String>,
    /// Optional DOG (Directive Order Group) to assign this order to.
    pub dog_id: Option<Uuid>,
}

/// Default empty JSON array for labels.
fn default_empty_labels() -> serde_json::Value {
    serde_json::json!([])
}

/// Request to update an existing order.
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateOrderRequest {
    pub title: Option<String>,
    pub description: Option<String>,
    pub priority: Option<String>,
    pub status: Option<String>,
    pub order_type: Option<String>,
    pub labels: Option<serde_json::Value>,
    pub directive_id: Option<Uuid>,
    pub directive_step_id: Option<Uuid>,
    pub repository_url: Option<String>,
    /// Optional DOG (Directive Order Group) to assign/reassign this order to.
    pub dog_id: Option<Uuid>,
}

/// Response for order list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct OrderListResponse {
    pub orders: Vec<Order>,
    pub total: i64,
}

/// Query parameters for listing orders.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct OrderListQuery {
    /// Filter by status (e.g., "open", "in_progress", "under_review", "done", "archived")
    pub status: Option<String>,
    /// Filter by order type (e.g., "feature", "bug", "spike", "chore", "improvement")
    #[serde(rename = "type")]
    pub order_type: Option<String>,
    /// Filter by priority (e.g., "critical", "high", "medium", "low", "none")
    pub priority: Option<String>,
    /// Filter by linked directive ID
    pub directive_id: Option<Uuid>,
    /// Filter by DOG (Directive Order Group) ID
    pub dog_id: Option<Uuid>,
    /// Text search across title, description, and directive_name (case-insensitive)
    pub search: Option<String>,
}

/// Request body for linking an order to a directive.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct LinkDirectiveRequest {
    pub directive_id: Uuid,
}

// =============================================================================
// Directive Order Group (DOG) Types
// =============================================================================

/// A Directive Order Group (DOG) — an epic-like grouping of orders within a directive.
/// DOGs allow organizing related orders under a common theme or goal.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveOrderGroup {
    pub id: Uuid,
    pub directive_id: Uuid,
    pub owner_id: Uuid,
    pub name: String,
    pub description: Option<String>,
    /// Status: open, in_progress, done, archived
    pub status: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

/// Request to create a new Directive Order Group (DOG).
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreateDirectiveOrderGroupRequest {
    pub name: String,
    pub description: Option<String>,
}

/// Request to update a Directive Order Group (DOG).
#[derive(Debug, Default, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDirectiveOrderGroupRequest {
    pub name: Option<String>,
    pub description: Option<String>,
    pub status: Option<String>,
}

/// Response for DOG list endpoint.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveOrderGroupListResponse {
    pub dogs: Vec<DirectiveOrderGroup>,
    pub total: i64,
}