from pydantic import BaseModel, Field, field_validator from datetime import datetime, date from typing import Optional, List from app.schemas.category import CategoryResponse from app.schemas.task import TaskResponse # ============ GoalStep Schemas ============ class GoalStepBase(BaseModel): title: str = Field(..., max_length=200) step_type: str = Field(..., pattern="^(phase|milestone)$") status: str = Field(default="pending", pattern="^(pending|in_progress|completed)$") target_date: Optional[date] = None parent_id: Optional[int] = None sort_order: int = Field(default=0) class GoalStepCreate(GoalStepBase): pass class GoalStepUpdate(BaseModel): title: Optional[str] = Field(None, max_length=200) step_type: Optional[str] = Field(None, pattern="^(phase|milestone)$") status: Optional[str] = Field(None, pattern="^(pending|in_progress|completed)$") target_date: Optional[date] = None parent_id: Optional[int] = None sort_order: Optional[int] = None @property def clearable_fields(self) -> set: return {"target_date", "parent_id"} class GoalStepResponse(GoalStepBase): id: int goal_id: int reached_at: Optional[datetime] = None created_at: datetime children: List["GoalStepResponse"] = [] class Config: from_attributes = True # ============ GoalReview Schemas ============ class GoalReviewCreate(BaseModel): content: str = Field(..., min_length=1) rating: Optional[int] = Field(None, ge=1, le=5) class GoalReviewResponse(BaseModel): id: int goal_id: int content: str rating: Optional[int] = None created_at: datetime class Config: from_attributes = True # ============ Goal Schemas ============ class GoalBase(BaseModel): title: str = Field(..., max_length=200) description: Optional[str] = None status: str = Field(default="active", pattern="^(active|paused|completed|abandoned)$") target_date: Optional[date] = None category_id: Optional[int] = None color: str = Field(default="#FFB7C5", max_length=20) icon: str = Field(default="flag", max_length=50) sort_order: int = Field(default=0) class GoalCreate(GoalBase): pass class GoalUpdate(BaseModel): title: Optional[str] = Field(None, max_length=200) description: Optional[str] = None status: Optional[str] = Field(None, pattern="^(active|paused|completed|abandoned)$") target_date: Optional[date] = None category_id: Optional[int] = None color: Optional[str] = Field(None, max_length=20) icon: Optional[str] = Field(None, max_length=50) sort_order: Optional[int] = None @property def clearable_fields(self) -> set: return {"description", "target_date", "category_id"} class GoalListResponse(GoalBase): id: int progress: int completed_at: Optional[datetime] = None created_at: datetime updated_at: datetime category: Optional[CategoryResponse] = None total_steps: int = 0 completed_steps: int = 0 class Config: from_attributes = True class GoalDetailResponse(GoalListResponse): steps: List[GoalStepResponse] = [] reviews: List[GoalReviewResponse] = [] tasks: List[TaskResponse] = [] class GoalStatusUpdate(BaseModel): status: str = Field(..., pattern="^(active|paused|completed|abandoned)$")