// Bridge: queryx Expr / Query → foxql builders and AST.
//
// Usage:
//   1. Implement FieldResolver for your table proxy struct.
//   2. Call to_foxql_expr() or to_select() to convert.

// ---------------------------------------------------------------------------
// Error
// ---------------------------------------------------------------------------

///|
pub suberror QueryxError {
  Unsupported(String)
  UnknownField(String)
  EmptyAnd
  EmptyOr
} derive(Debug)

// ---------------------------------------------------------------------------
// Value conversion
// ---------------------------------------------------------------------------

///|
/// Convert a queryx Value to a foxql Value.
pub fn to_foxql_value(v : Value) -> @ast.Value {
  match v {
    Value::Null => @ast.Value::Null
    Value::Bool(b) => @ast.Value::Bool(b)
    Value::Int(n) => @ast.Value::Int(n.to_int())
    Value::Float(f) => @ast.Value::Float(f)
    Value::String(s) => @ast.Value::String(s)
  }
}

// ---------------------------------------------------------------------------
// Operator conversion
// ---------------------------------------------------------------------------

///|
/// Convert a queryx CmpOp to a foxql Operator.
pub fn to_foxql_op(op : CmpOp) -> @ast.Operator {
  match op {
    CmpOp::Eq => @ast.Operator::Eq
    CmpOp::Ne => @ast.Operator::Neq
    CmpOp::Gt => @ast.Operator::Gt
    CmpOp::Lt => @ast.Operator::Lt
    CmpOp::Ge => @ast.Operator::Gte
    CmpOp::Le => @ast.Operator::Lte
  }
}

// ---------------------------------------------------------------------------
// Expr conversion
// ---------------------------------------------------------------------------

///|
/// Convert a queryx Expr to a foxql Expr.
pub fn[R : @foxql.FieldResolver] to_foxql_expr(
  expr : Expr,
  resolver : R,
) -> @ast.Expr raise QueryxError {
  match expr {
    Expr::Empty =>
      raise QueryxError::Unsupported(
        "Empty expression cannot be converted to SQL",
      )
    Expr::Lit(_) =>
      raise QueryxError::Unsupported(
        "Literal expression cannot be converted to SQL",
      )
    Expr::Field(_) =>
      raise QueryxError::Unsupported(
        "Field expression cannot be converted to SQL",
      )
    Expr::Compare(field, op, val) => {
      let col = match resolver.resolve_column(field) {
        Some(c) => c
        None => raise QueryxError::UnknownField(field)
      }
      @ast.Expr::Compare(col, to_foxql_op(op), to_foxql_value(val))
    }
    Expr::Between(field, lo, hi) => {
      let col = match resolver.resolve_column(field) {
        Some(c) => c
        None => raise QueryxError::UnknownField(field)
      }
      @ast.Expr::Between(col, to_foxql_value(lo), to_foxql_value(hi))
    }
    Expr::StrMatch(field, kind, pattern) => {
      let col = match resolver.resolve_column(field) {
        Some(c) => c
        None => raise QueryxError::UnknownField(field)
      }
      let (op, sql_pattern) = strmatch_to_like(kind, pattern)
      @ast.Expr::Compare(col, op, @ast.Value::String(sql_pattern))
    }
    Expr::And(sub_exprs) => {
      let converted = collect_exprs(sub_exprs, resolver)
      if converted.length() == 0 {
        raise QueryxError::EmptyAnd
      } else if converted.length() == 1 {
        converted[0]
      } else {
        @ast.Expr::And(converted)
      }
    }
    Expr::Or(sub_exprs) => {
      let converted = collect_exprs(sub_exprs, resolver)
      if converted.length() == 0 {
        raise QueryxError::EmptyOr
      } else if converted.length() == 1 {
        converted[0]
      } else {
        @ast.Expr::Or(converted)
      }
    }
    Expr::Not(inner) => @ast.Expr::Not(to_foxql_expr(inner, resolver))
  }
}

///|
fn[R : @foxql.FieldResolver] collect_exprs(
  exprs : Array[Expr],
  resolver : R,
) -> Array[@ast.Expr] raise QueryxError {
  let converted : Array[@ast.Expr] = []
  for e in exprs {
    let c = to_foxql_expr(e, resolver)
    converted.push(c)
  }
  converted
}

// ---------------------------------------------------------------------------
// StrMatch → LIKE conversion
// ---------------------------------------------------------------------------

///|
fn strmatch_to_like(
  kind : StrKind,
  pattern : String,
) -> (@ast.Operator, String) {
  match kind {
    StrKind::Contains => (@ast.Operator::Like, "%" + pattern + "%")
    StrKind::StartsWith => (@ast.Operator::Like, pattern + "%")
    StrKind::EndsWith => (@ast.Operator::Like, "%" + pattern)
  }
}

// ---------------------------------------------------------------------------
// Query → foxql builder
// ---------------------------------------------------------------------------

///|
/// Convert a queryx Query to a foxql SelectBuilder (WHERE, ORDER BY, LIMIT).
/// The resolver provides both field→column mapping and the table name.
pub fn[R : @foxql.FieldResolver] to_select(
  query : Query,
  resolver : R,
) -> @builder.SelectBuilder raise QueryxError {
  let table = @builder.Table::new(resolver.table_name())
  let builder = @builder.SelectBuilder::new(table, [])
  let filtered = if query.filter is Expr::Empty {
    builder
  } else {
    builder.where_(to_foxql_expr(query.filter, resolver))
  }
  let ordered = match query.order_by {
    Some(order) => apply_order(filtered, order, resolver)
    None => filtered
  }
  match query.page {
    Some(page) => ordered.limit(page.size)
    None => ordered
  }
}

///|
fn[R : @foxql.FieldResolver] apply_order(
  builder : @builder.SelectBuilder,
  order : Order,
  resolver : R,
) -> @builder.SelectBuilder {
  let s = order.0
  if s == "" {
    return builder
  }
  let parts = split_order(s)
  apply_order_parts(builder, parts, 0, resolver)
}

///|
fn[R : @foxql.FieldResolver] apply_order_parts(
  builder : @builder.SelectBuilder,
  parts : Array[String],
  i : Int,
  resolver : R,
) -> @builder.SelectBuilder {
  if i >= parts.length() {
    builder
  } else {
    let kv = split_order_kv(parts[i])
    let next = if kv.length() >= 2 {
      match resolver.resolve_column(kv[0]) {
        Some(col) => {
          let clause = if kv[1] == "desc" {
            @ast.OrderByClause::{
              column: col,
              direction: @ast.OrderDirection::Desc,
            }
          } else {
            @ast.OrderByClause::{
              column: col,
              direction: @ast.OrderDirection::Asc,
            }
          }
          builder.order_by(clause)
        }
        None => builder
      }
    } else {
      builder
    }
    apply_order_parts(next, parts, i + 1, resolver)
  }
}

///|
fn split_order(s : String) -> Array[String] {
  split_order_rec(s, 0, 0, [])
}

///|
fn split_order_rec(
  s : String,
  start : Int,
  pos : Int,
  acc : Array[String],
) -> Array[String] {
  if pos >= s.length() {
    let last = s[start:].trim().to_owned()
    if last != "" {
      acc.push(last)
    }
    acc
  } else if s[pos:pos + 1] == "," {
    let part = s[start:pos].trim().to_owned()
    if part != "" {
      acc.push(part)
    }
    split_order_rec(s, pos + 1, pos + 1, acc)
  } else {
    split_order_rec(s, start, pos + 1, acc)
  }
}

///|
fn split_order_kv(s : String) -> Array[String] {
  split_order_kv_rec(s, 0, 0, [])
}

///|
fn split_order_kv_rec(
  s : String,
  start : Int,
  pos : Int,
  acc : Array[String],
) -> Array[String] {
  if pos >= s.length() {
    acc.push(s[start:].trim().to_owned())
    acc
  } else if s[pos:pos + 1] == ":" {
    acc.push(s[start:pos].trim().to_owned())
    split_order_kv_rec(s, pos + 1, pos + 1, acc)
  } else {
    split_order_kv_rec(s, start, pos + 1, acc)
  }
}