///|
/// Build a JSON introspection view of an executable schema.
///
/// `Full` returns root operation types, type metadata, field/input/enum details,
/// union members, and directives. `TypeNamesOnly` is a compact view for
/// diagnostics and simple tooling.
pub fn[Ctx] introspect(
  schema : ExecutableSchema[Ctx],
  format~ : IntrospectionFormat = Full,
) -> Json {
  match format {
    TypeNamesOnly => {
      let names = schema.registry.types.keys().to_array()
      Json::object({
        "types": Json::array(names.map(name => Json::string(name))),
        "queryType": Json::string(schema.query_type),
      })
    }
    Full => {
      let types = schema.registry.types
        .keys()
        .to_array()
        .map(name => {
          let type_def = schema.registry.types[name]
          Json::object({
            "name": Json::string(name),
            "kind": Json::string(type_kind_name(type_def)),
            "fields": Json::array(introspection_fields(type_def)),
            "enumValues": Json::array(introspection_enum_values(type_def)),
            "inputFields": Json::array(introspection_input_fields(type_def)),
            "possibleTypes": Json::array(introspection_possible_types(type_def)),
          })
        })
      Json::object({
        "queryType": Json::object({ "name": Json::string(schema.query_type) }),
        "mutationType": option_type_ref(schema.mutation_type),
        "subscriptionType": option_type_ref(schema.subscription_type),
        "types": Json::array(types),
        "directives": Json::array(
          schema.registry.directives
          .keys()
          .to_array()
          .map(key => {
            let directive = schema.registry.directives[key]
            Json::object({
              "name": Json::string(directive.name),
              "locations": Json::array(
                directive.locations.map(loc => Json::string(loc)),
              ),
              "isRepeatable": Json::boolean(directive.repeatable),
              "args": Json::array(
                directive.arguments.map(arg => {
                  Json::object({
                    "name": Json::string(arg.name),
                    "type": Json::string(type_to_string(arg.value_type)),
                  })
                }),
              ),
            })
          }),
        ),
      })
    }
  }
}

///|
fn option_type_ref(type_name : String?) -> Json {
  match type_name {
    Some(name) => Json::object({ "name": Json::string(name) })
    None => Json::null()
  }
}

///|
fn type_kind_name(type_def : TypeDef) -> String {
  match type_def {
    ScalarDef(_) => "SCALAR"
    ObjectDef(_) => "OBJECT"
    InterfaceDef(_) => "INTERFACE"
    UnionDef(_) => "UNION"
    EnumDef(_) => "ENUM"
    InputObjectDef(_) => "INPUT_OBJECT"
  }
}

///|
fn introspection_fields(type_def : TypeDef) -> Array[Json] {
  match type_def {
    ObjectDef(fields~, ..) | InterfaceDef(fields~, ..) =>
      fields
      .values()
      .to_array()
      .map(field => {
        Json::object({
          "name": Json::string(field.name),
          "type": Json::string(type_to_string(field.field_type)),
          "args": Json::array(
            field.arguments.map(arg => {
              Json::object({
                "name": Json::string(arg.name),
                "type": Json::string(type_to_string(arg.value_type)),
              })
            }),
          ),
        })
      })
    _ => []
  }
}

///|
fn introspection_enum_values(type_def : TypeDef) -> Array[Json] {
  match type_def {
    EnumDef(values~, ..) =>
      values.map(value => Json::object({ "name": Json::string(value) }))
    _ => []
  }
}

///|
fn introspection_input_fields(type_def : TypeDef) -> Array[Json] {
  match type_def {
    InputObjectDef(fields~, ..) =>
      fields
      .values()
      .to_array()
      .map(field => {
        Json::object({
          "name": Json::string(field.name),
          "type": Json::string(type_to_string(field.value_type)),
        })
      })
    _ => []
  }
}

///|
fn introspection_possible_types(type_def : TypeDef) -> Array[Json] {
  match type_def {
    UnionDef(members~, ..) =>
      members.map(name => Json::object({ "name": Json::string(name) }))
    _ => []
  }
}