///|
/// A scalar or reference expression accepted by an EvoWitness field.
pub(all) enum TypeExpr {
  StringType
  IntType
  BoolType
  NumberType
  EnumType(Array[String])
  RefType(String)
  ListType(String)
} derive(Eq, Debug)

///|
/// Validation constraints attached to one field.
pub(all) struct Constraints {
  min_int : Int?
  max_int : Int?
  min_len : Int?
  max_len : Int?
  default_value : String?
} derive(Eq, Debug)

///|
/// Construct an unconstrained field rule.
pub fn Constraints::none() -> Constraints {
  {
    min_int: None,
    max_int: None,
    min_len: None,
    max_len: None,
    default_value: None,
  }
}

///|
/// One named field in an object contract.
pub(all) struct Field {
  name : String
  type_expr : TypeExpr
  required : Bool
  constraints : Constraints
  line : Int
} derive(Eq, Debug)

///|
/// One object type. Open objects accept unknown fields; closed objects reject them.
pub(all) struct ObjectType {
  name : String
  open : Bool
  fields : Array[Field]
  line : Int
} derive(Eq, Debug)

///|
/// A complete named and versioned contract.
pub(all) struct Contract {
  name : String
  version : String
  objects : Array[ObjectType]
} derive(Eq, Debug)

///|
/// Stable parser diagnostic suitable for editors and CI logs.
pub(all) struct ParseError {
  line : Int
  column : Int
  code : String
  message : String
  source_line : String
} derive(Eq, Debug)

///|
/// Return the object named `name` when it exists.
pub fn Contract::find_object(self : Contract, name : String) -> ObjectType? {
  for object in self.objects {
    if object.name == name {
      return Some(object)
    }
  }
  None
}

///|
/// Return the field named `name` when it exists.
pub fn ObjectType::find_field(self : ObjectType, name : String) -> Field? {
  for field in self.fields {
    if field.name == name {
      return Some(field)
    }
  }
  None
}

///|
/// Check whether this object contains a field with the given name.
pub fn ObjectType::has_field(self : ObjectType, name : String) -> Bool {
  self.find_field(name) is Some(_)
}

///|
/// Render a compact, stable type spelling used in diagnostics.
pub fn TypeExpr::render(self : TypeExpr) -> String {
  match self {
    StringType => "string"
    IntType => "int"
    BoolType => "bool"
    NumberType => "number"
    EnumType(values) => "enum:" + values.join("|")
    RefType(name) => "ref:" + name
    ListType(item) => "list:" + item
  }
}

///|
/// Whether the expression points to another object in the contract.
pub fn TypeExpr::referenced_name(self : TypeExpr) -> String? {
  match self {
    RefType(name) => Some(name)
    ListType(item) =>
      if item.has_prefix("ref:") {
        Some(item[4:].to_owned())
      } else {
        None
      }
    _ => None
  }
}

///|
/// Return a machine-stable path for a field.
pub fn field_path(object_name : String, field_name : String) -> String {
  "$." + object_name + "." + field_name
}

///|
/// Create a parser error without exposing parser internals.
fn parse_error(
  line : Int,
  code : String,
  message : String,
  source_line : String,
) -> ParseError {
  { line, column: 1, code, message, source_line }
}