//! Global scope context helpers: SQL fragment generation and project listing.
//!
//! The [`ScopeContext`] struct lives in [`state::mod`] and holds the active
//! project filter. This module provides:
//!
//! - [`scope_filter_sql`] — generates a SQL WHERE clause fragment
//! - [`fetch_projects`] — lists available projects for the scope picker
//!
//! Action functions already accept `project_id: Option<i64>` — callers pass
//! `scope.project_id` directly. The helpers here are for screens that build
//! custom SQL or need the project list for UI.

use anyhow::{Context, Result};
use rusqlite::Connection;

/// Project metadata for the scope picker overlay.
#[derive(Debug, Clone)]
pub struct ProjectInfo {
    /// Internal database ID (projects.id).
    pub id: i64,
    /// GitLab path (e.g., "group/repo").
    pub path: String,
}

/// Generate a SQL WHERE clause fragment that filters by project_id.
///
/// Returns an empty string for `None` (all projects), or
/// `" AND {table_alias}.project_id = {id}"` for `Some(id)`.
///
/// The leading `AND` makes it safe to append to an existing WHERE clause.
///
/// # Examples
///
/// ```ignore
/// let filter = scope_filter_sql(Some(42), "mr");
/// assert_eq!(filter, " AND mr.project_id = 42");
///
/// let filter = scope_filter_sql(None, "mr");
/// assert_eq!(filter, "");
/// ```
#[must_use]
pub fn scope_filter_sql(project_id: Option<i64>, table_alias: &str) -> String {
    match project_id {
        Some(id) => format!(" AND {table_alias}.project_id = {id}"),
        None => String::new(),
    }
}

/// Fetch all projects from the database for the scope picker.
///
/// Returns projects sorted by path. Used to populate the scope picker
/// overlay when the user presses `P`.
pub fn fetch_projects(conn: &Connection) -> Result<Vec<ProjectInfo>> {
    let mut stmt = conn
        .prepare("SELECT id, path_with_namespace FROM projects ORDER BY path_with_namespace")
        .context("preparing projects query")?;

    let projects = stmt
        .query_map([], |row| {
            Ok(ProjectInfo {
                id: row.get(0)?,
                path: row.get(1)?,
            })
        })
        .context("querying projects")?
        .filter_map(std::result::Result::ok)
        .collect();

    Ok(projects)
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

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

    #[test]
    fn test_scope_filter_sql_none_returns_empty() {
        let sql = scope_filter_sql(None, "mr");
        assert_eq!(sql, "");
    }

    #[test]
    fn test_scope_filter_sql_some_returns_and_clause() {
        let sql = scope_filter_sql(Some(42), "mr");
        assert_eq!(sql, " AND mr.project_id = 42");
    }

    #[test]
    fn test_scope_filter_sql_different_alias() {
        let sql = scope_filter_sql(Some(7), "mfc");
        assert_eq!(sql, " AND mfc.project_id = 7");
    }

    #[test]
    fn test_fetch_projects_empty_db() {
        let conn = Connection::open_in_memory().unwrap();
        conn.execute_batch(
            "CREATE TABLE projects (
                id INTEGER PRIMARY KEY,
                gitlab_project_id INTEGER UNIQUE NOT NULL,
                path_with_namespace TEXT NOT NULL
            )",
        )
        .unwrap();

        let projects = fetch_projects(&conn).unwrap();
        assert!(projects.is_empty());
    }

    #[test]
    fn test_fetch_projects_returns_sorted() {
        let conn = Connection::open_in_memory().unwrap();
        conn.execute_batch(
            "CREATE TABLE projects (
                id INTEGER PRIMARY KEY,
                gitlab_project_id INTEGER UNIQUE NOT NULL,
                path_with_namespace TEXT NOT NULL
            )",
        )
        .unwrap();

        conn.execute(
            "INSERT INTO projects (id, gitlab_project_id, path_with_namespace) VALUES (1, 100, 'z-group/repo')",
            [],
        )
        .unwrap();
        conn.execute(
            "INSERT INTO projects (id, gitlab_project_id, path_with_namespace) VALUES (2, 200, 'a-group/repo')",
            [],
        )
        .unwrap();

        let projects = fetch_projects(&conn).unwrap();
        assert_eq!(projects.len(), 2);
        assert_eq!(projects[0].path, "a-group/repo");
        assert_eq!(projects[0].id, 2);
        assert_eq!(projects[1].path, "z-group/repo");
        assert_eq!(projects[1].id, 1);
    }

    #[test]
    fn test_scope_filter_sql_composable_in_query() {
        // Verify the fragment works when embedded in a full SQL statement.
        let project_id = Some(5);
        let filter = scope_filter_sql(project_id, "mr");
        let sql = format!(
            "SELECT * FROM merge_requests mr WHERE mr.state = 'merged'{filter} ORDER BY mr.updated_at"
        );
        assert!(sql.contains("AND mr.project_id = 5"));
    }
}
