use crate::calcit::{Calcit, CalcitErr, CalcitErrKind};

pub fn binary_equal(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
  match (xs.first(), xs.get(1)) {
    (Some(a), Some(b)) => Ok(Calcit::Bool(a == b)),
    (_, _) => {
      let hint = String::from(
        "💡 Usage: `&= value1 value2`\n  Compares two values for equality\n  Works on any types (numbers, strings, lists, etc.)",
      );
      crate::builtins::err_arity_with_hint("&= requires exactly 2 arguments to compare, but received:", xs, hint)
    }
  }
}

pub fn binary_less(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
  if xs.len() == 2 {
    match (&xs[0], &xs[1]) {
      (Calcit::Number(a), Calcit::Number(b)) => Ok(Calcit::Bool(a < b)),
      _ => {
        let msg = format!(
          "&< expects numbers, but received: {} and {}",
          crate::builtins::meta::type_of(&[xs[0].clone()])?.lisp_str(),
          crate::builtins::meta::type_of(&[xs[1].clone()])?.lisp_str()
        );
        let hint =
          String::from("💡 Usage: `&< number1 number2`\n  Compares if first number is less than second\n  Example: `&< 3 5` => true");
        CalcitErr::err_str_with_hint(CalcitErrKind::Type, msg, hint)
      }
    }
  } else {
    let hint =
      String::from("💡 Usage: `&< number1 number2`\n  Compares if first number is less than second\n  Example: `&< 3 5` => true");
    crate::builtins::err_arity_with_hint("&< requires exactly 2 arguments to compare, but received:", xs, hint)
  }
}

pub fn binary_greater(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
  if xs.len() == 2 {
    match (&xs[0], &xs[1]) {
      (Calcit::Number(a), Calcit::Number(b)) => Ok(Calcit::Bool(a > b)),
      _ => {
        let msg = format!(
          "&> expects numbers, but received: {} and {}",
          crate::builtins::meta::type_of(&[xs[0].clone()])?.lisp_str(),
          crate::builtins::meta::type_of(&[xs[1].clone()])?.lisp_str()
        );
        let hint = String::from(
          "💡 Usage: `&> number1 number2`\n  Compares if first number is greater than second\n  Example: `&> 5 3` => true",
        );
        CalcitErr::err_str_with_hint(CalcitErrKind::Type, msg, hint)
      }
    }
  } else {
    let hint =
      String::from("💡 Usage: `&> value1 value2`\n  Compares if first value is greater than second\n  Example: `&> 5 3` => true");
    crate::builtins::err_arity_with_hint("&> requires exactly 2 arguments to compare, but received:", xs, hint)
  }
}

pub fn not(xs: &[Calcit]) -> Result<Calcit, CalcitErr> {
  if xs.len() != 1 {
    let hint =
      String::from("💡 Usage: `not boolean-value`\n  Negates a boolean value\n  Examples: `not true` => false, `not nil` => true");
    return crate::builtins::err_arity_with_hint("not requires exactly 1 argument (a boolean or nil), but received:", xs, hint);
  }
  match &xs[0] {
    Calcit::Nil => Ok(Calcit::Bool(true)),
    Calcit::Bool(b) => Ok(Calcit::Bool(!b)),
    a => {
      let msg = format!(
        "not requires a boolean or nil as argument, but received: {}",
        crate::builtins::meta::type_of(&[a.clone()])?.lisp_str()
      );
      let hint =
        String::from("💡 Hint: Pass a boolean value (true/false) or nil to not\n  Examples: `not true` => false, `not nil` => true");
      crate::builtins::err_type_with_hint(msg, hint)
    }
  }
}
