use super::{DateTime, ProjectId, Result, UserId, Utc, Uuid, async_trait}; /// Type of item in search results. #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "lowercase")] pub enum SearchResultType { /// A task item. Task, /// An email item. Email, /// A project item. Project, /// A calendar event. Event, /// A contact. Contact, } /// A single search result item with ranking information. #[derive(Debug, Clone, serde::Serialize)] pub struct SearchResultItem { /// Item ID. pub id: Uuid, /// Type of the result. pub result_type: SearchResultType, /// Display title. pub title: String, /// Text snippet showing match context. pub snippet: Option, /// Associated project ID if any. pub project_id: Option, /// Associated project name if any. pub project_name: Option, /// Relevance rank (higher is better). pub rank: f64, } /// Search query with filtering and pagination options. #[derive(Debug, Clone, Default)] pub struct SearchQuery { /// The search terms (FTS text after filters extracted). pub query: String, /// Filter to specific result types. pub types: Option>, /// Filter to a specific project by ID. pub project_id: Option, /// Filter to a specific project by name (partial match). pub project_name: Option, /// Filter to items on or after this date. pub date_from: Option>, /// Filter to items on or before this date. pub date_to: Option>, /// Maximum results to return. pub limit: Option, /// Offset for pagination. pub offset: Option, /// `is:` filters for time/state conditions. pub is_filters: Vec, /// Priority filter (`priority:high` etc). pub priority: Option, /// Tags to include (`tag:name`). pub tags_include: Vec, /// Tags to exclude (`-tag:name`). pub tags_exclude: Vec, } impl SearchQuery { /// Creates a new search query with default options. pub fn new(query: impl Into) -> Self { Self { query: query.into(), types: None, project_id: None, project_name: None, date_from: None, date_to: None, limit: Some(50), offset: None, is_filters: Vec::new(), priority: None, tags_include: Vec::new(), tags_exclude: Vec::new(), } } /// Filters results to specific types. #[must_use] pub fn with_types(mut self, types: Vec) -> Self { self.types = Some(types); self } /// Filters results to a specific project by ID. #[must_use] pub fn with_project(mut self, project_id: ProjectId) -> Self { self.project_id = Some(project_id); self } /// Filters results to a specific project by name. #[must_use] pub fn with_project_name(mut self, name: impl Into) -> Self { self.project_name = Some(name.into()); self } /// Sets the maximum number of results. Negative values are clamped to 0: /// SQLite treats a negative LIMIT as unbounded, so an unclamped negative /// would silently disable the cap instead of enforcing it. #[must_use] pub fn with_limit(mut self, limit: i64) -> Self { self.limit = Some(limit.max(0)); self } /// Sets the pagination offset. Negative values are clamped to 0. #[must_use] pub fn with_offset(mut self, offset: i64) -> Self { self.offset = Some(offset.max(0)); self } /// Filters results to items on or after this date. #[must_use] pub fn with_date_from(mut self, date: DateTime) -> Self { self.date_from = Some(date); self } /// Filters results to items on or before this date. #[must_use] pub fn with_date_to(mut self, date: DateTime) -> Self { self.date_to = Some(date); self } /// Adds `is:` filters. #[must_use] pub fn with_is_filters(mut self, filters: Vec) -> Self { self.is_filters = filters; self } /// Sets priority filter. #[must_use] pub fn with_priority(mut self, priority: crate::models::Priority) -> Self { self.priority = Some(priority); self } /// Sets tags to include. #[must_use] pub fn with_tags_include(mut self, tags: Vec) -> Self { self.tags_include = tags; self } /// Sets tags to exclude. #[must_use] pub fn with_tags_exclude(mut self, tags: Vec) -> Self { self.tags_exclude = tags; self } } /// Repository for full-text search across all content types. #[async_trait] pub trait SearchRepository: Send + Sync { /// Searches across all indexed content using FTS. /// Returns (results, total_count) where total_count is the pre-pagination count. async fn search( &self, user_id: UserId, query: SearchQuery, ) -> Result<(Vec, usize)>; }