///|
/// The declared key or value type of a MAP (RFC 0104). The names are the
/// static type names of partiql-tests' `$map::::::` encoding.
pub(all) enum MapType {
  Dynamic
  Bool
  TinyInt
  SmallInt
  Integer
  BigInt
  Numeric
  Decimal
  Real
  Double
  String
  Char
  Varchar
  Date
  Time
  Timestamp
} derive(Debug, Eq)

///|
/// The type name as partiql-tests writes it.
pub fn MapType::name(self : MapType) -> String {
  match self {
    Dynamic => "dynamic"
    Bool => "bool"
    TinyInt => "tinyint"
    SmallInt => "smallint"
    Integer => "integer"
    BigInt => "bigint"
    Numeric => "numeric"
    Decimal => "decimal"
    Real => "real"
    Double => "double"
    String => "string"
    Char => "char"
    Varchar => "varchar"
    Date => "date"
    Time => "time"
    Timestamp => "timestamp"
  }
}

///|
/// The type for a name; `int` is accepted for `integer`.
pub fn MapType::from_name(name : String) -> MapType? {
  match name {
    "dynamic" => Some(Dynamic)
    "bool" => Some(Bool)
    "tinyint" => Some(TinyInt)
    "smallint" => Some(SmallInt)
    "integer" | "int" => Some(Integer)
    "bigint" => Some(BigInt)
    "numeric" => Some(Numeric)
    "decimal" => Some(Decimal)
    "real" => Some(Real)
    "double" => Some(Double)
    "string" => Some(String)
    "char" => Some(Char)
    "varchar" => Some(Varchar)
    "date" => Some(Date)
    "time" => Some(Time)
    "timestamp" => Some(Timestamp)
    _ => None
  }
}

///|
/// Why a MAP could not be built.
pub(all) suberror MapError {
  /// A NULL, MISSING, non-scalar or unlowerable key.
  InvalidKey(String)
  /// Two keys that are equal under `eqg`.
  DuplicateKey(String)
  /// RFC 0104 leaves a DYNAMIC key type for future work.
  DynamicKeyType
} derive(Debug, Eq)

///|
/// A PartiQL MAP: unique scalar keys, no MISSING values. The stored order
/// is the insertion order (for output); equality and ordering ignore it.
pub struct MapValue {
  key_type : MapType
  value_type : MapType
  priv entries : Array[(Value, Value)]
} derive(Debug)

///|
fn is_scalar_key(key : Value) -> Bool {
  key
  is (Bool(_)
  | Int(_)
  | BigInt(_)
  | Float(_)
  | Decimal(_)
  | String(_)
  | Clob(_)
  | Blob(_)
  | Date(_)
  | Time(_)
  | Timestamp(_)
  | IntervalYM(_)
  | IntervalDT(_))
}

///|
/// Builds a MAP. Entries whose value is MISSING are dropped (RFC 0104).
/// Keys are lowered first; NULL, MISSING and non-scalar keys, and keys equal
/// under `eqg` to an earlier key, raise.
pub fn MapValue::new(
  key_type~ : MapType,
  value_type~ : MapType,
  entries : Array[(Value, Value)],
) -> MapValue raise MapError {
  if key_type == Dynamic {
    raise DynamicKeyType
  }
  // Validate every key first, also those whose value is MISSING: a strict
  // constructor rejects an invalid or duplicate key in any entry.
  let keys : Array[Value] = []
  for entry in entries {
    let key = entry.0.lower() catch {
      _ => raise InvalidKey("a symbol without text")
    }
    if !is_scalar_key(key) {
      raise InvalidKey(@debug.to_string(key))
    }
    if keys.iter().any(k => eqg(k, key)) {
      raise DuplicateKey(@debug.to_string(key))
    }
    keys.push(key)
  }
  let kept : Array[(Value, Value)] = []
  for k, entry in entries {
    if !(entry.1 is Missing) {
      kept.push((keys[k], entry.1))
    }
  }
  { key_type, value_type, entries: kept, }
}

///|
pub fn MapValue::entries(self : MapValue) -> ArrayView[(Value, Value)] {
  self.entries[:]
}

///|
pub fn MapValue::length(self : MapValue) -> Int {
  self.entries.length()
}