// The runtime value model of the executor: the Python objects that the code generated by
// sqlglot's Python executor manipulates (see sqlglot/executor/env.py).

///|
/// A Python exception raised while evaluating generated code, e.g.
/// `PyException("ZeroDivisionError", "division by zero")`.
pub(all) suberror PyException {
  PyException(String, String)
}

///|
pub fn PyException::kind(self : PyException) -> String {
  match self {
    PyException(k, _) => k
  }
}

///|
/// Python `str(exception)`.
pub fn PyException::message(self : PyException) -> String {
  match self {
    PyException(_, m) => m
  }
}

///|
fn type_error(msg : String) -> PyException {
  PyException("TypeError", msg)
}

///|
fn value_error(msg : String) -> PyException {
  PyException("ValueError", msg)
}

///|
/// A Python callable (an ENV function, a lambda or a builtin).
pub(all) struct PyFunc {
  name : String
  call : (Array[Value]) -> Value raise
}

///|
/// A runtime value (a Python object).
pub(all) enum Value {
  /// `None` (SQL NULL)
  Null
  Bool(Bool)
  Int(Int64)
  Float(Double)
  Str(String)
  List(Array[Value])
  Tuple(Array[Value])
  /// An insertion-ordered `dict`.
  Dict(Array[(Value, Value)])
  /// A `set`, in CPython's iteration order (see pyset.mbt).
  Set(Array[Value])
  Date(PyDate)
  Time(PyTime)
  DateTime(PyDateTime)
  TimeDelta(PyTimeDelta)
  /// A generator / iterator (materialized).
  Iter(Array[Value])
  Func(PyFunc)
  /// A module or namespace object: `exp` or `exp.DType`.
  Module(String)
  /// A member of `exp.DType`.
  DTypeV(@core.DType)
  /// The `scope` mapping of table names to readers.
  Readers(Map[String?, Reader])
  /// A table reader (`RowReader` or `RangeReader`).
  ReaderV(Reader)
  /// `env.reverse_key`
  RevKey(Value)
}

///|
/// A reader of the current row(s) of a table.
pub enum Reader {
  Row(RowReader)
  Range(RangeReader)
}

///|
/// Python `type(value).__name__` (as used in error messages).
pub fn Value::type_name(self : Value) -> String {
  match self {
    Null => "NoneType"
    Bool(_) => "bool"
    Int(_) => "int"
    Float(_) => "float"
    Str(_) => "str"
    List(_) => "list"
    Tuple(_) => "tuple"
    Dict(_) => "dict"
    Set(_) => "set"
    Date(_) => "datetime.date"
    Time(_) => "datetime.time"
    DateTime(_) => "datetime.datetime"
    TimeDelta(_) => "datetime.timedelta"
    Iter(_) => "generator"
    Func(_) => "function"
    Module(_) => "module"
    DTypeV(_) => "DType"
    Readers(_) => "dict"
    ReaderV(Row(_)) => "RowReader"
    ReaderV(Range(_)) => "RangeReader"
    RevKey(_) => "reverse_key"
  }
}

///|
/// Python truthiness (`bool(value)`).
pub fn Value::truthy(self : Value) -> Bool {
  match self {
    Null => false
    Bool(b) => b
    Int(i) => i != 0L
    Float(d) => d != 0.0
    Str(s) => !s.is_empty()
    List(l) | Tuple(l) | Set(l) => !l.is_empty()
    Dict(d) => !d.is_empty()
    TimeDelta(td) => !td.is_zero()
    Readers(m) => !m.is_empty()
    _ => true
  }
}

///|
pub fn Value::is_null(self : Value) -> Bool {
  self is Null
}

///|
/// Python `repr(value)`.
pub fn Value::repr(self : Value) -> String {
  match self {
    Null => "None"
    Bool(b) => if b { "True" } else { "False" }
    Int(i) => i.to_string()
    Float(d) => float_repr(d)
    Str(s) => @core.py_repr_str(s)
    List(l) => "[" + l.map(x => x.repr()).join(", ") + "]"
    Iter(l) => " x.repr()).join(", ") + "]>"
    Tuple(l) =>
      if l.length() == 1 {
        "(" + l[0].repr() + ",)"
      } else {
        "(" + l.map(x => x.repr()).join(", ") + ")"
      }
    Set(l) =>
      if l.is_empty() {
        "set()"
      } else {
        "{" + l.map(x => x.repr()).join(", ") + "}"
      }
    Dict(d) =>
      "{" + d.map(kv => kv.0.repr() + ": " + kv.1.repr()).join(", ") + "}"
    Date(d) => d.repr()
    Time(t) => t.repr()
    DateTime(dt) => dt.repr()
    TimeDelta(td) => td.repr()
    Func(f) => ""
    Module(m) => ""
    DTypeV(t) => ""
    Readers(_) => ""
    ReaderV(_) => ""
    RevKey(v) => ""
  }
}

///|
/// Python `str(value)`.
pub fn Value::py_str(self : Value) -> String {
  match self {
    Str(s) => s
    Date(d) => d.isoformat()
    Time(t) => t.isoformat()
    DateTime(dt) => dt.isoformat(sep=" ")
    TimeDelta(td) => td.py_str()
    DTypeV(t) => "DType.\{t.name()}"
    _ => self.repr()
  }
}

///|
pub impl Show for Value with fn output(self, logger) {
  logger.write_string(self.repr())
}

///|
/// Python `==` (never raises).
pub fn py_eq(a : Value, b : Value) -> Bool {
  match (a, b) {
    (Null, Null) => true
    (Null, _) | (_, Null) => false
    (Str(x), Str(y)) => x == y
    (Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
      num_cmp_eq(a, b)
    (List(x), List(y)) | (Tuple(x), Tuple(y)) => seq_eq(x, y)
    (Set(x), Set(y)) =>
      x.length() == y.length() &&
      x.iter().all(v => y.iter().any(w => py_eq(v, w)))
    (Dict(x), Dict(y)) =>
      x.length() == y.length() &&
      x
      .iter()
      .all(kv => y.iter().any(kw => py_eq(kv.0, kw.0) && py_eq(kv.1, kw.1)))
    (Date(x), Date(y)) => x == y
    (Time(x), Time(y)) => x == y
    (DateTime(x), DateTime(y)) => x.eq(y)
    (TimeDelta(x), TimeDelta(y)) => x == y
    (DTypeV(x), DTypeV(y)) => x == y
    (RevKey(x), RevKey(y)) => py_eq(y, x)
    (Module(x), Module(y)) => x == y
    (Func(x), Func(y)) => physical_equal(x, y)
    (Iter(x), Iter(y)) => physical_equal(x, y)
    _ => false
  }
}

///|
fn seq_eq(x : Array[Value], y : Array[Value]) -> Bool {
  if x.length() != y.length() {
    return false
  }
  for i in 0.. (Bool, Int64, Double) {
  match v {
    Bool(b) => (false, if b { 1L } else { 0L }, if b { 1.0 } else { 0.0 })
    Int(i) => (false, i, i.to_double())
    Float(d) => (true, 0L, d)
    _ => (false, 0L, 0.0)
  }
}

///|
/// Exact comparison of an integer with a double (Python semantics): -1, 0, 1, or
/// `None` when the double is NaN.
fn cmp_int_double(i : Int64, d : Double) -> Int? {
  if d.is_nan() {
    return None
  }
  if d.is_pos_inf() {
    return Some(-1)
  }
  if d.is_neg_inf() {
    return Some(1)
  }
  let f = d.floor()
  // |i| < 2^63 so a double >= 2^63 is larger
  if f >= 9223372036854775808.0 {
    return Some(-1)
  }
  if f < -9223372036854775808.0 {
    return Some(1)
  }
  let fi = f.to_int64()
  if i < fi {
    Some(-1)
  } else if i > fi {
    Some(1)
  } else if d > f {
    Some(-1)
  } else {
    Some(0)
  }
}

///|
/// Python numeric comparison: -1, 0, 1 or `None` (NaN involved).
fn num_cmp(a : Value, b : Value) -> Int? {
  let (af, ai, ad) = num_parts(a)
  let (bf, bi, bd) = num_parts(b)
  match (af, bf) {
    (false, false) => Some(ai.compare(bi))
    (true, true) =>
      if ad.is_nan() || bd.is_nan() {
        None
      } else if ad < bd {
        Some(-1)
      } else if ad > bd {
        Some(1)
      } else {
        Some(0)
      }
    (false, true) => cmp_int_double(ai, bd)
    (true, false) => cmp_int_double(bi, ad).map(c => -c)
  }
}

///|
fn num_cmp_eq(a : Value, b : Value) -> Bool {
  num_cmp(a, b) is Some(0)
}

///|
fn unorderable(op : String, a : Value, b : Value) -> PyException {
  type_error(
    "'\{op}' not supported between instances of '\{a.type_name()}' and '\{b.type_name()}'",
  )
}

///|
/// Python `a < b`.
pub fn py_lt(a : Value, b : Value) -> Bool raise PyException {
  match (a, b) {
    (Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
      num_cmp(a, b) is Some(-1)
    (Str(x), Str(y)) => str_cmp(x, y) < 0
    (List(x), List(y)) | (Tuple(x), Tuple(y)) => seq_lt(x, y)
    (Date(x), Date(y)) => x.compare(y) < 0
    (Time(x), Time(y)) => x.compare(y) < 0
    (DateTime(x), DateTime(y)) => x.compare(y) < 0
    (TimeDelta(x), TimeDelta(y)) => x.compare(y) < 0
    (RevKey(x), RevKey(y)) => py_lt(y, x)
    _ => raise unorderable("<", a, b)
  }
}

///|
/// Python `a > b`.
pub fn py_gt(a : Value, b : Value) -> Bool raise PyException {
  match (a, b) {
    (RevKey(_), RevKey(_)) => py_lt(b, a)
    (Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
      num_cmp(a, b) is Some(1)
    _ =>
      py_lt(b, a) catch {
        PyException("TypeError", _) => raise unorderable(">", a, b)
        e => raise e
      }
  }
}

///|
/// Python `a <= b`.
pub fn py_le(a : Value, b : Value) -> Bool raise PyException {
  match (a, b) {
    (Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
      num_cmp(a, b) is Some(-1 | 0)
    (List(x), List(y)) | (Tuple(x), Tuple(y)) => seq_le(x, y)
    _ =>
      (py_eq(a, b) && comparable_types(a, b)) ||
      (py_lt(a, b) catch {
        PyException("TypeError", _) => raise unorderable("<=", a, b)
        e => raise e
      })
  }
}

///|
/// Python `a >= b`.
pub fn py_ge(a : Value, b : Value) -> Bool raise PyException {
  match (a, b) {
    (Bool(_) | Int(_) | Float(_), Bool(_) | Int(_) | Float(_)) =>
      num_cmp(a, b) is Some(1 | 0)
    _ =>
      py_le(b, a) catch {
        PyException("TypeError", _) => raise unorderable(">=", a, b)
        e => raise e
      }
  }
}

///|
fn comparable_types(a : Value, b : Value) -> Bool {
  match (a, b) {
    (Str(_), Str(_))
    | (Date(_), Date(_))
    | (Time(_), Time(_))
    | (DateTime(_), DateTime(_))
    | (TimeDelta(_), TimeDelta(_)) => true
    _ => false
  }
}

///|
/// Lexicographic comparison of two sequences with Python's rich comparison rules.
fn seq_lt(x : Array[Value], y : Array[Value]) -> Bool raise PyException {
  let n = if x.length() < y.length() { x.length() } else { y.length() }
  for i in 0.. Bool raise PyException {
  let n = if x.length() < y.length() { x.length() } else { y.length() }
  for i in 0.. Int {
  let ai = a.iter()
  let bi = b.iter()
  for ;; {
    match (ai.next(), bi.next()) {
      (None, None) => return 0
      (None, Some(_)) => return -1
      (Some(_), None) => return 1
      (Some(x), Some(y)) => if x != y { return if x < y { -1 } else { 1 } }
    }
  }
}

///|
/// Python `iter(value)`, materialized.
pub fn py_iter(v : Value) -> Array[Value] raise PyException {
  match v {
    List(l) | Tuple(l) | Set(l) | Iter(l) => l
    Dict(d) => d.map(kv => kv.0)
    Str(s) => s.iter().map(c => Str(c.to_string())).collect()
    _ => raise type_error("'\{v.type_name()}' object is not iterable")
  }
}

///|
/// Python `len(value)`.
pub fn py_len(v : Value) -> Int raise PyException {
  match v {
    Str(s) => s.char_length()
    List(l) | Tuple(l) | Set(l) => l.length()
    Dict(d) => d.length()
    Readers(m) => m.length()
    _ => raise type_error("object of type '\{v.type_name()}' has no len()")
  }
}

///|
/// Python `x[key]` for dicts, lists, tuples and strings.
pub fn py_getitem(container : Value, key : Value) -> Value raise PyException {
  match container {
    Dict(d) => {
      for kv in d {
        if py_eq(kv.0, key) {
          return kv.1
        }
      }
      raise PyException("KeyError", key.repr())
    }
    Readers(m) => {
      let k : String? = match key {
        Null => None
        Str(s) => Some(s)
        _ => raise PyException("KeyError", key.repr())
      }
      match m.get(k) {
        Some(r) => ReaderV(r)
        None => raise PyException("KeyError", key.repr())
      }
    }
    ReaderV(r) =>
      match key {
        Str(column) => r.get(column)
        _ => raise PyException("KeyError", key.repr())
      }
    List(l) | Tuple(l) => {
      let i = index_of(key, container)
      let n = l.length()
      let j = if i < 0 { i + n } else { i }
      if j < 0 || j >= n {
        raise PyException(
          "IndexError",
          "\{container.type_name()} index out of range",
        )
      }
      l[j]
    }
    Str(s) => {
      let chars = s.to_array()
      let i = index_of(key, container)
      let n = chars.length()
      let j = if i < 0 { i + n } else { i }
      if j < 0 || j >= n {
        raise PyException("IndexError", "string index out of range")
      }
      Str(chars[j].to_string())
    }
    _ =>
      raise type_error("'\{container.type_name()}' object is not subscriptable")
  }
}

///|
fn index_of(key : Value, container : Value) -> Int raise PyException {
  match key {
    Int(i) => i.to_int()
    Bool(b) => if b { 1 } else { 0 }
    _ =>
      raise type_error(
        "\{container.type_name()} indices must be integers or slices, not \{key.type_name()}",
      )
  }
}

///|
/// Python slicing `x[start:stop:step]` of a string, list or tuple.
pub fn py_slice(
  v : Value,
  start : Value,
  stop : Value,
  step? : Int = 1,
) -> Value raise PyException {
  let items : Array[Value] = match v {
    Str(s) => s.iter().map(c => Str(c.to_string())).collect()
    List(l) | Tuple(l) => l
    _ => raise type_error("'\{v.type_name()}' object is not subscriptable")
  }
  let n = items.length()
  fn bound(x : Value, default : Int) -> Int raise PyException {
    match x {
      Null => default
      Int(_) | Bool(_) => {
        let i = index_of(x, v)
        if step > 0 {
          if i < 0 {
            @cmp.maximum(i + n, 0)
          } else {
            @cmp.minimum(i, n)
          }
        } else if i < 0 {
          @cmp.maximum(i + n, -1)
        } else {
          @cmp.minimum(i, n - 1)
        }
      }
      _ =>
        raise type_error(
          "slice indices must be integers or None or have an __index__ method",
        )
    }
  }

  let out = []
  if step > 0 {
    let s = bound(start, 0)
    let e = bound(stop, n)
    for i = s; i < e; i = i + step {
      out.push(items[i])
    }
  } else {
    let s = bound(start, n - 1)
    let e = bound(stop, -1)
    for i = s; i > e; i = i + step {
      out.push(items[i])
    }
  }
  match v {
    Str(_) => Str(out.map(x => x.py_str()).join(""))
    Tuple(_) => Tuple(out)
    _ => List(out)
  }
}

///|
/// Python `==`.
pub impl Eq for Value with fn equal(self, other) {
  py_eq(self, other)
}