use crate::{
    condition::{Condition, ConditionKind},
    group::{Conjunction, Group, Node},
    order::Order,
    value::Value,
};

#[derive(Clone)]
pub struct Query {
    pub(crate) nodes: Vec<Node>,
    pub(crate) order: Option<(String, Order)>,
    pub(crate) limit: Option<u64>,
    pub(crate) offset: Option<u64>,
}

impl Query {
    pub fn new() -> Self {
        Self { nodes: vec![], order: None, limit: None, offset: None }
    }

    fn push(mut self, kind: ConditionKind, values: Vec<Value>) -> Self {
        self.nodes.push(Node::Cond(Condition { kind, values }));
        self
    }

    pub fn eq(self, col: &str, val: impl Into<Value>) -> Self {
        self.push(ConditionKind::Eq(col.into()), vec![val.into()])
    }

    pub fn ne(self, col: &str, val: impl Into<Value>) -> Self {
        self.push(ConditionKind::Ne(col.into()), vec![val.into()])
    }

    pub fn gt(self, col: &str, val: impl Into<Value>) -> Self {
        self.push(ConditionKind::Gt(col.into()), vec![val.into()])
    }

    pub fn gte(self, col: &str, val: impl Into<Value>) -> Self {
        self.push(ConditionKind::Gte(col.into()), vec![val.into()])
    }

    pub fn lt(self, col: &str, val: impl Into<Value>) -> Self {
        self.push(ConditionKind::Lt(col.into()), vec![val.into()])
    }

    pub fn lte(self, col: &str, val: impl Into<Value>) -> Self {
        self.push(ConditionKind::Lte(col.into()), vec![val.into()])
    }

    pub fn is_null(self, col: &str) -> Self {
        self.push(ConditionKind::IsNull(col.into()), vec![])
    }

    pub fn is_not_null(self, col: &str) -> Self {
        self.push(ConditionKind::IsNotNull(col.into()), vec![])
    }

    pub fn filter_optional(self, col: &str, val: Option<impl Into<Value>>) -> Self {
        let v = val.map(Into::into).unwrap_or(Value::Null);
        self.push(ConditionKind::FilterOptional(col.into()), vec![v])
    }

    pub fn filter_optional_gte(self, col: &str, val: Option<impl Into<Value>>) -> Self {
        let v = val.map(Into::into).unwrap_or(Value::Null);
        self.push(ConditionKind::FilterOptionalGte(col.into()), vec![v])
    }

    pub fn filter_optional_lte(self, col: &str, val: Option<impl Into<Value>>) -> Self {
        let v = val.map(Into::into).unwrap_or(Value::Null);
        self.push(ConditionKind::FilterOptionalLte(col.into()), vec![v])
    }

    pub fn in_<T: Into<Value>>(self, col: &str, vals: Vec<T>) -> Self {
        let values = vals.into_iter().map(Into::into).collect();
        self.push(ConditionKind::In(col.into()), values)
    }

    pub fn not_in<T: Into<Value>>(self, col: &str, vals: Vec<T>) -> Self {
        let values = vals.into_iter().map(Into::into).collect();
        self.push(ConditionKind::NotIn(col.into()), values)
    }

    pub fn like(self, col: &str, pat: impl Into<Value>) -> Self {
        self.push(ConditionKind::Like(col.into()), vec![pat.into()])
    }

    pub fn not_like(self, col: &str, pat: impl Into<Value>) -> Self {
        self.push(ConditionKind::NotLike(col.into()), vec![pat.into()])
    }

    pub fn between(self, col: &str, lo: impl Into<Value>, hi: impl Into<Value>) -> Self {
        self.push(ConditionKind::Between(col.into()), vec![lo.into(), hi.into()])
    }

    pub fn or(mut self, f: impl FnOnce(Query) -> Query) -> Self {
        let inner = f(Query::new());
        self.nodes.push(Node::Group(Group { conjunction: Conjunction::Or, nodes: inner.nodes }));
        self
    }

    pub fn and(mut self, f: impl FnOnce(Query) -> Query) -> Self {
        let inner = f(Query::new());
        self.nodes.push(Node::Group(Group { conjunction: Conjunction::And, nodes: inner.nodes }));
        self
    }

    pub fn order_by(mut self, col: &str, dir: Order) -> Self {
        self.order = Some((col.into(), dir));
        self
    }

    pub fn limit(mut self, n: u64) -> Self { self.limit = Some(n); self }
    pub fn offset(mut self, n: u64) -> Self { self.offset = Some(n); self }
}

impl Default for Query {
    fn default() -> Self { Self::new() }
}
