use std::collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::contracts::{
Model, ModelEffort, PhaseOverrides, ReasoningEffort, Runner, RunnerCliOptionsPatch,
};
use super::priority::TaskPriority;
use super::serde_helpers::{
custom_fields_schema, deserialize_custom_fields, model_effort_is_default, model_effort_schema,
};
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Task {
pub id: String,
#[serde(default)]
pub status: TaskStatus,
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub priority: TaskPriority,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub scope: Vec<String>,
#[serde(default)]
pub evidence: Vec<String>,
#[serde(default)]
pub plan: Vec<String>,
#[serde(default)]
pub notes: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<TaskAgent>,
#[schemars(required)]
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<String>,
#[schemars(required)]
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub started_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub estimated_minutes: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actual_minutes: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scheduled_start: Option<String>,
#[serde(default)]
pub depends_on: Vec<String>,
#[serde(default)]
pub blocks: Vec<String>,
#[serde(default)]
pub relates_to: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub duplicates: Option<String>,
#[serde(default, deserialize_with = "deserialize_custom_fields")]
#[schemars(schema_with = "custom_fields_schema")]
pub custom_fields: HashMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_id: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Default, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Draft,
#[default]
Todo,
Doing,
Done,
Rejected,
}
impl TaskStatus {
pub fn as_str(self) -> &'static str {
match self {
TaskStatus::Draft => "draft",
TaskStatus::Todo => "todo",
TaskStatus::Doing => "doing",
TaskStatus::Done => "done",
TaskStatus::Rejected => "rejected",
}
}
}
impl std::fmt::Display for TaskStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
#[serde(deny_unknown_fields)]
pub struct TaskAgent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runner: Option<Runner>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<Model>,
#[serde(default, skip_serializing_if = "model_effort_is_default")]
#[schemars(schema_with = "model_effort_schema")]
pub model_effort: ModelEffort,
#[schemars(range(min = 1, max = 3))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub phases: Option<u8>,
#[schemars(range(min = 1))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iterations: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub followup_reasoning_effort: Option<ReasoningEffort>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub runner_cli: Option<RunnerCliOptionsPatch>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub phase_overrides: Option<PhaseOverrides>,
}