///|
/// Policy for `x-rust-type` extensions naming crates not configured with
/// `with_crate`.
pub(all) enum UnknownPolicy {
  /// Generate the type from the schema.
  Generate
  /// Use the named type by path.
  Allow
  /// Currently behaves like `Generate`.
  Deny
} derive(Eq, Debug)

///|
/// A crate version to accept for `x-rust-type`.
pub(all) enum CrateVers {
  Version(@semver.Version)
  Any
  Never
} derive(Debug)

///|
/// Parse `!` (never), `*` (any) or a semver version.
pub fn CrateVers::parse(s : String) -> CrateVers? {
  match s {
    "!" => Some(Never)
    "*" => Some(Any)
    _ =>
      try @semver.Version::parse(s) catch {
        _ => None
      } noraise {
        v => Some(Version(v))
      }
  }
}

///|
priv struct CrateSpec {
  version : CrateVers
  rename : String?
}

///|
/// Modifications applied to a generated type with a given name.
pub struct TypeSpacePatch {
  mut rename : String?
  derives : Array[String]
  attrs : Array[String]
} derive(Debug)

///|
pub fn TypeSpacePatch::new() -> TypeSpacePatch {
  { rename: None, derives: [], attrs: [], }
}

///|
pub fn TypeSpacePatch::with_rename(
  self : TypeSpacePatch,
  rename : String,
) -> TypeSpacePatch {
  self.rename = Some(rename)
  self
}

///|
pub fn TypeSpacePatch::with_derive(
  self : TypeSpacePatch,
  derive_name : String,
) -> TypeSpacePatch {
  self.derives.push(derive_name)
  self
}

///|
pub fn TypeSpacePatch::with_attr(
  self : TypeSpacePatch,
  attr : String,
) -> TypeSpacePatch {
  self.attrs.push(attr)
  self
}

///|
/// A replacement of a named definition by an existing type.
pub(all) struct TypeSpaceReplace {
  replace_type : String
  impls : Array[TypeSpaceImpl]
} derive(Debug)

///|
priv struct TypeSpaceConversion {
  schema : @schema.SchemaObject
  type_name : String
  impls : Array[TypeSpaceImpl]
}

///|
/// Settings that alter type generation (upstream `TypeSpaceSettings`).
pub struct TypeSpaceSettings {
  mut type_mod : String?
  extra_derives : Array[String]
  extra_attrs : Array[String]
  mut struct_builder : Bool
  mut unknown_crates : UnknownPolicy
  priv crates : Map[String, CrateSpec]
  /// The map type path, e.g. `::std::collections::HashMap`.
  mut map_type : String
  patch : Map[String, TypeSpacePatch]
  replace : Map[String, TypeSpaceReplace]
  priv convert : Array[TypeSpaceConversion]
}

///|
pub fn TypeSpaceSettings::default() -> TypeSpaceSettings {
  {
    type_mod: None,
    extra_derives: [],
    extra_attrs: [],
    struct_builder: false,
    unknown_crates: Generate,
    crates: Map([]),
    map_type: "::std::collections::HashMap",
    patch: Map([]),
    replace: Map([]),
    convert: [],
  }
}

///|
fn TypeSpaceSettings::copy(self : TypeSpaceSettings) -> TypeSpaceSettings {
  {
    type_mod: self.type_mod,
    extra_derives: self.extra_derives.copy(),
    extra_attrs: self.extra_attrs.copy(),
    struct_builder: self.struct_builder,
    unknown_crates: self.unknown_crates,
    crates: self.crates.copy(),
    map_type: self.map_type,
    patch: self.patch.copy(),
    replace: self.replace.copy(),
    convert: self.convert.copy(),
  }
}

///|
/// Path prefix for types defined in the type space.
pub fn TypeSpaceSettings::with_type_mod(
  self : TypeSpaceSettings,
  type_mod : String,
) -> TypeSpaceSettings {
  self.type_mod = Some(type_mod)
  self
}

///|
/// Add a derive macro applied to all defined types.
pub fn TypeSpaceSettings::with_derive(
  self : TypeSpaceSettings,
  derive_name : String,
) -> TypeSpaceSettings {
  if !self.extra_derives.contains(derive_name) {
    self.extra_derives.push(derive_name)
  }
  self
}

///|
/// Add an attribute applied to all defined types.
pub fn TypeSpaceSettings::with_attr(
  self : TypeSpaceSettings,
  attr : String,
) -> TypeSpaceSettings {
  if !self.extra_attrs.contains(attr) {
    self.extra_attrs.push(attr)
  }
  self
}

///|
/// Generate builder types for structs.
pub fn TypeSpaceSettings::with_struct_builder(
  self : TypeSpaceSettings,
  struct_builder : Bool,
) -> TypeSpaceSettings {
  self.struct_builder = struct_builder
  self
}

///|
/// Replace a named definition with an existing type (last one wins).
pub fn TypeSpaceSettings::with_replacement(
  self : TypeSpaceSettings,
  type_name : String,
  replace_type : String,
  impls : Array[TypeSpaceImpl],
) -> TypeSpaceSettings {
  self.replace[type_name] = { replace_type, impls, }
  self
}

///|
/// Patch a generated type by name (last one wins).
pub fn TypeSpaceSettings::with_patch(
  self : TypeSpaceSettings,
  type_name : String,
  patch : TypeSpacePatch,
) -> TypeSpaceSettings {
  self.patch[type_name] = patch
  self
}

///|
/// Map schemas exactly equal to `schema` (ignoring metadata) to a named type
/// (first one wins).
pub fn TypeSpaceSettings::with_conversion(
  self : TypeSpaceSettings,
  schema : @schema.SchemaObject,
  type_name : String,
  impls : Array[TypeSpaceImpl],
) -> TypeSpaceSettings {
  self.convert.push({ schema, type_name, impls, })
  self
}

///|
pub fn TypeSpaceSettings::with_unknown_crates(
  self : TypeSpaceSettings,
  policy : UnknownPolicy,
) -> TypeSpaceSettings {
  self.unknown_crates = policy
  self
}

///|
/// Accept `x-rust-type` types from `crate_name` at `version`, optionally
/// renaming the crate in paths.
pub fn TypeSpaceSettings::with_crate(
  self : TypeSpaceSettings,
  crate_name : String,
  version : CrateVers,
  rename? : String,
) -> TypeSpaceSettings {
  self.crates[crate_name] = { version, rename, }
  self
}

///|
/// The map-like type used in generated code.
pub fn TypeSpaceSettings::with_map_type(
  self : TypeSpaceSettings,
  map_type : String,
) -> TypeSpaceSettings {
  self.map_type = map_type
  self
}