use crate::condition::Condition;
use crate::values::Value;

/// A SQL fragment with embedded parameters.
/// Produced by the `sql!()` macro for non-condition expressions (e.g., SELECT items).
#[derive(Debug, Clone, PartialEq)]
pub struct SqlFragment {
    pub sql: String,
    pub params: Vec<Value>,
}

impl SqlFragment {
    pub fn new(sql: impl Into<String>, params: Vec<Value>) -> Self {
        Self {
            sql: sql.into(),
            params,
        }
    }

    pub fn raw(sql: impl Into<String>) -> Self {
        Self {
            sql: sql.into(),
            params: Vec::new(),
        }
    }
}

/// A SQL condition produced by the `sql!()` macro.
/// Convertible to `Condition::Raw`.
#[derive(Debug, Clone, PartialEq)]
pub struct SqlCondition {
    pub sql: String,
    pub params: Vec<Value>,
}

impl SqlCondition {
    pub fn new(sql: impl Into<String>, params: Vec<Value>) -> Self {
        Self {
            sql: sql.into(),
            params,
        }
    }
}

impl From<SqlCondition> for Condition {
    fn from(sc: SqlCondition) -> Self {
        Condition::Raw {
            sql: sc.sql,
            params: sc.params,
        }
    }
}

/// Raw SQL marker (no parameterization). Used for ORDER direction, DISTINCT, etc.
#[derive(Debug, Clone, PartialEq)]
pub struct SqlRaw(pub String);

impl SqlRaw {
    pub fn new(s: impl Into<String>) -> Self {
        Self(s.into())
    }
}

/// Column reference with table qualification. Used in JOINs.
#[derive(Debug, Clone, PartialEq)]
pub struct SqlRef {
    pub table: String,
    pub column: String,
}

impl SqlRef {
    pub fn new(table: impl Into<String>, column: impl Into<String>) -> Self {
        Self {
            table: table.into(),
            column: column.into(),
        }
    }

    pub fn to_sql(&self) -> String {
        format!("{}.{}", self.table, self.column)
    }
}

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

    #[test]
    fn test_sql_fragment() {
        let frag = SqlFragment::new("COUNT(*)", vec![]);
        assert_eq!(frag.sql, "COUNT(*)");
        assert!(frag.params.is_empty());
    }

    #[test]
    fn test_sql_condition_into_condition() {
        let sc = SqlCondition::new("age > ?", vec![Value::Int(18)]);
        let cond: Condition = sc.into();
        match cond {
            Condition::Raw { sql, params } => {
                assert_eq!(sql, "age > ?");
                assert_eq!(params, vec![Value::Int(18)]);
            }
            _ => panic!("Expected Raw condition"),
        }
    }

    #[test]
    fn test_sql_ref() {
        let r = SqlRef::new("users", "id");
        assert_eq!(r.to_sql(), "users.id");
    }
}
