///|
/// JSON Schema types supported by MCP tool input/output schemas.
pub(all) enum SchemaType {
  SchemaObject
  SchemaString
  SchemaNumber
  SchemaInteger
  SchemaBoolean
  SchemaArray
} derive(Debug, Eq)

///|
/// Convert a [SchemaType] to its JSON string representation.
fn SchemaType::to_string(self : SchemaType) -> String {
  match self {
    SchemaObject => "object"
    SchemaString => "string"
    SchemaNumber => "number"
    SchemaInteger => "integer"
    SchemaBoolean => "boolean"
    SchemaArray => "array"
  }
}

///|
/// A property definition inside an object schema.
pub(all) struct SchemaProperty {
  typ : SchemaType
  description : String?
} derive(Debug, Eq)

///|
/// A builder for constructing a JSON Schema (the subset used by MCP tools).
pub(all) struct JsonSchema {
  mut typ : SchemaType
  mut description : String?
  mut properties : Map[String, SchemaProperty]
  mut required : Array[String]
} derive(Debug, Eq)

///|
/// Create a new object schema (the root of every tool input/output schema).
pub fn JsonSchema::object() -> JsonSchema {
  { typ: SchemaObject, description: None, properties: Map([]), required: [] }
}

///|
/// Set a human-readable description for the schema.
pub fn JsonSchema::describe(self : JsonSchema, desc : String) -> JsonSchema {
  self.description = Some(desc)
  self
}

///|
/// Add a required string property to an object schema.
pub fn JsonSchema::string(self : JsonSchema, name : String) -> JsonSchema {
  self.properties[name] = { typ: SchemaString, description: None }
  self.required.push(name)
  self
}

///|
/// Add a required string property with a description.
pub fn JsonSchema::string_desc(
  self : JsonSchema,
  name : String,
  desc : String,
) -> JsonSchema {
  self.properties[name] = { typ: SchemaString, description: Some(desc) }
  self.required.push(name)
  self
}

///|
/// Add a required number property.
pub fn JsonSchema::number(self : JsonSchema, name : String) -> JsonSchema {
  self.properties[name] = { typ: SchemaNumber, description: None }
  self.required.push(name)
  self
}

///|
/// Add a required integer property.
pub fn JsonSchema::integer(self : JsonSchema, name : String) -> JsonSchema {
  self.properties[name] = { typ: SchemaInteger, description: None }
  self.required.push(name)
  self
}

///|
/// Add a required boolean property.
pub fn JsonSchema::boolean(self : JsonSchema, name : String) -> JsonSchema {
  self.properties[name] = { typ: SchemaBoolean, description: None }
  self.required.push(name)
  self
}

///|
/// Add an optional string property (not added to required).
pub fn JsonSchema::optional_string(
  self : JsonSchema,
  name : String,
) -> JsonSchema {
  self.properties[name] = { typ: SchemaString, description: None }
  self
}

///|
/// Encode a [JsonSchema] to a JSON value.
pub fn JsonSchema::to_json(self : JsonSchema) -> Json {
  let obj : Map[String, Json] = Map([])
  obj["type"] = Json::string(self.typ.to_string())
  match self.description {
    Some(d) => obj["description"] = Json::string(d)
    None => ()
  }
  // properties
  let props : Map[String, Json] = Map([])
  for k in self.properties.keys() {
    let p = self.properties[k]
    let p_obj : Map[String, Json] = Map([])
    p_obj["type"] = Json::string(p.typ.to_string())
    match p.description {
      Some(d) => p_obj["description"] = Json::string(d)
      None => ()
    }
    props[k] = Json::object(p_obj)
  }
  if props.length() > 0 {
    obj["properties"] = Json::object(props)
  }
  // required
  if self.required.length() > 0 {
    obj["required"] = Json::array(self.required.map(fn(s) { Json::string(s) }))
  }
  Json::object(obj)
}