use modql::{
filter::ListOptions,
filter::{FilterGroups, OpVal, OpValBool, OpValFloat64, OpValInt64, OpValString},
};
use std::collections::BTreeMap;
use surrealdb::sql::Value;
use crate::prelude::*;
pub(super) fn surreal_query_builder(
tb: &str,
or_groups: Option<FilterGroups>,
list_options: ListOptions,
) -> RustiumResult<(String, BTreeMap<String, Value>)> {
let mut sql = String::from("SELECT * FROM type::table($tb)");
let mut vars = BTreeMap::from([("tb".into(), tb.into())]);
if let Some(or_groups) = or_groups {
let mut idx = 0;
sql.push_str(" WHERE");
for (group_idx, filter_nodes) in or_groups.groups().iter().enumerate() {
if group_idx > 0 {
sql.push_str(" OR");
}
sql.push_str(" (");
for (node_idx, filter_node) in filter_nodes.nodes().iter().enumerate() {
let key = &filter_node.name;
for opval in &filter_node.opvals {
let var = f!("w{idx}");
if node_idx > 0 {
sql.push_str(" AND");
}
let (sql_el, val) = sqlize(opval.clone(), key, &var)?;
sql.push_str(&f!(" {sql_el}"));
vars.insert(var, val);
idx += 1;
}
}
sql.push_str(" )");
}
}
if let Some(order_bys) = list_options.order_bys {
sql.push_str(" ORDER BY ");
let obs = order_bys
.order_bys()
.into_iter()
.map(|o| o.to_string())
.collect::<Vec<String>>();
let obs = obs.join(",");
sql.push_str(&obs);
}
if let Some(limit) = list_options.limit {
sql.push_str(&f!(" LIMIT {limit}"));
}
if let Some(offset) = list_options.offset {
sql.push_str(&f!(" START {offset}"));
}
Ok((sql, vars))
}
fn sqlize(opval: OpVal, prop_name: &str, var_idx: &str) -> RustiumResult<(String, Value)> {
Ok(match opval {
OpVal::String(OpValString::Eq(v)) => (f!("{prop_name} = ${var_idx}"), v.into()),
OpVal::Int64(OpValInt64::Eq(v)) => (f!("{prop_name} = ${var_idx}"), v.into()),
OpVal::Float64(OpValFloat64::Eq(v)) => (f!("{prop_name} = ${var_idx}"), v.into()),
OpVal::Bool(OpValBool::Eq(v)) => (f!("{prop_name} = ${var_idx}"), v.into()),
OpVal::String(OpValString::Not(v)) => (f!("{prop_name} != ${var_idx}"), v.into()),
OpVal::Int64(OpValInt64::Not(v)) => (f!("{prop_name} != ${var_idx}"), v.into()),
OpVal::Float64(OpValFloat64::Not(v)) => (f!("{prop_name} != ${var_idx}"), v.into()),
OpVal::Bool(OpValBool::Not(v)) => (f!("{prop_name} != ${var_idx}"), v.into()),
OpVal::String(OpValString::Lt(v)) => (f!("{prop_name} < ${var_idx}"), v.into()),
OpVal::Int64(OpValInt64::Lt(v)) => (f!("{prop_name} < ${var_idx}"), v.into()),
OpVal::Float64(OpValFloat64::Lt(v)) => (f!("{prop_name} < ${var_idx}"), v.into()),
OpVal::String(OpValString::Lte(v)) => (f!("{prop_name} < ${var_idx}"), v.into()),
OpVal::Int64(OpValInt64::Lte(v)) => (f!("{prop_name} < ${var_idx}"), v.into()),
OpVal::Float64(OpValFloat64::Lte(v)) => (f!("{prop_name} < ${var_idx}"), v.into()),
OpVal::String(OpValString::Gt(v)) => (f!("{prop_name} > ${var_idx}"), v.into()),
OpVal::Int64(OpValInt64::Gt(v)) => (f!("{prop_name} > ${var_idx}"), v.into()),
OpVal::Float64(OpValFloat64::Gt(v)) => (f!("{prop_name} > ${var_idx}"), v.into()),
OpVal::String(OpValString::Gte(v)) => (f!("{prop_name} > ${var_idx}"), v.into()),
OpVal::Int64(OpValInt64::Gte(v)) => (f!("{prop_name} > ${var_idx}"), v.into()),
OpVal::Float64(OpValFloat64::Gte(v)) => (f!("{prop_name} > ${var_idx}"), v.into()),
OpVal::String(OpValString::Contains(v)) => {
(f!("{prop_name} CONTAINS ${var_idx}"), v.into())
}
OpVal::String(OpValString::StartsWith(v)) => {
(f!("string::startsWith({prop_name}, ${var_idx}) "), v.into())
}
OpVal::String(OpValString::EndsWith(v)) => {
(f!("string::endsWith({prop_name}, ${var_idx}) "), v.into())
}
_ => return Err(RustiumError::ModqlOperatorNotSupported(f!("{opval:?}"))),
})
}