///|
/// In-memory PostgreSQL type registry — mirrors pgx's `pgtype.Map`.
/// Stores OID ↔ name mappings so we never query `pg_type` at runtime.
/// No Codec — just the type metadata. Codec support can be layered on later.

///|
/// A registered PostgreSQL data type.
pub(all) struct Type {
  name : String
  oid : Int
}

///|
/// Registry of PostgreSQL type OID ↔ name mappings.
/// * `oid_to_type` / `name_to_type` — canonical PG names (e.g. `int4`, `varchar`).
/// * `data_type_to_oid` — `information_schema` standard names (e.g. `integer`,
///   `character varying`) mapping to OIDs.
pub(all) struct TypeMap {
  oid_to_type : @hashmap.HashMap[Int, Type]
  name_to_type : @hashmap.HashMap[String, Type]
  data_type_to_oid : @hashmap.HashMap[String, Int]
}

///|
fn new_type(name : String, oid : Int) -> Type {
  Type::{ name, oid }
}

///|
fn TypeMap::new() -> TypeMap {
  {
    oid_to_type: @hashmap.HashMap([], capacity=200),
    name_to_type: @hashmap.HashMap([], capacity=200),
    data_type_to_oid: @hashmap.HashMap([], capacity=50),
  }
}

///|
fn TypeMap::register(self : TypeMap, name : String, oid : Int) -> TypeMap {
  let t = new_type(name, oid)
  self.oid_to_type.set(oid, Type::{ ..t })
  self.name_to_type.set(name, Type::{ ..t })
  self
}

///|
/// Register an `information_schema.data_type` alias for the given OID.
fn TypeMap::register_data_type(
  self : TypeMap,
  name : String,
  oid : Int,
) -> TypeMap {
  self.data_type_to_oid.set(name, oid)
  self
}

///|
/// Look up a type by OID. Returns `None` for unknown OIDs.
pub fn TypeMap::type_for_oid(self : TypeMap, oid : Int) -> Type? {
  self.oid_to_type.get(oid)
}

///|
/// Look up a type by canonical PG name. Returns `None` for unknown names.
pub fn TypeMap::type_for_name(self : TypeMap, name : String) -> Type? {
  self.name_to_type.get(name)
}

///|
/// Iterate over all registered types. Yields `(oid, Type)` pairs.
pub fn TypeMap::iter(self : TypeMap) -> Iter[(Int, Type)] {
  self.oid_to_type.iter()
}

///|
/// Iterate over all `information_schema.data_type` aliases.
/// Yields `(name, oid)` pairs.
pub fn TypeMap::iter_data_types(self : TypeMap) -> Iter[(String, Int)] {
  self.data_type_to_oid.iter()
}

///|
/// Look up a type by `information_schema.data_type` name.
/// Returns `None` for unknown names.
pub fn TypeMap::type_for_data_type(self : TypeMap, name : String) -> Type? {
  match self.data_type_to_oid.get(name) {
    None => None
    Some(oid) => self.oid_to_type.get(oid)
  }
}

// ---------------------------------------------------------------------------
// OID constants — for compile-time reference (e.g. @wire.int4_oid)
// ---------------------------------------------------------------------------

///|
/// Base types
pub let bool_oid : Int = 16

///|
pub let bytea_oid : Int = 17

///|
pub let char_oid : Int = 18

///|
pub let name_oid : Int = 19

///|
pub let int8_oid : Int = 20

///|
pub let int2_oid : Int = 21

///|
pub let int4_oid : Int = 23

///|
pub let text_oid : Int = 25

///|
pub let oid_oid : Int = 26

///|
pub let tid_oid : Int = 27

///|
pub let xid_oid : Int = 28

///|
pub let cid_oid : Int = 29

///|
pub let json_oid : Int = 114

///|
pub let xml_oid : Int = 142

///|
pub let point_oid : Int = 600

///|
pub let lseg_oid : Int = 601

///|
pub let path_oid : Int = 602

///|
pub let box_oid : Int = 603

///|
pub let polygon_oid : Int = 604

///|
pub let line_oid : Int = 628

///|
pub let cidr_oid : Int = 650

///|
pub let float4_oid : Int = 700

///|
pub let float8_oid : Int = 701

///|
pub let unknown_oid : Int = 705

///|
pub let circle_oid : Int = 718

///|
pub let macaddr8_oid : Int = 774

///|
pub let macaddr_oid : Int = 829

///|
pub let inet_oid : Int = 869

///|
pub let aclitem_oid : Int = 1033

///|
pub let bpchar_oid : Int = 1042

///|
pub let varchar_oid : Int = 1043

///|
pub let date_oid : Int = 1082

///|
pub let time_oid : Int = 1083

///|
pub let timestamp_oid : Int = 1114

///|
pub let timestamptz_oid : Int = 1184

///|
pub let interval_oid : Int = 1186

///|
pub let timetz_oid : Int = 1266

///|
pub let bit_oid : Int = 1560

///|
pub let varbit_oid : Int = 1562

///|
pub let numeric_oid : Int = 1700

///|
pub let record_oid : Int = 2249

///|
pub let uuid_oid : Int = 2950

///|
pub let tsvector_oid : Int = 3614

///|
pub let jsonb_oid : Int = 3802

///|
pub let jsonpath_oid : Int = 4072

///|
pub let xid8_oid : Int = 5069

///|
/// Range types
pub let int4range_oid : Int = 3904

///|
pub let numrange_oid : Int = 3906

///|
pub let tsrange_oid : Int = 3908

///|
pub let tstzrange_oid : Int = 3910

///|
pub let daterange_oid : Int = 3912

///|
pub let int8range_oid : Int = 3926

///|
/// Multirange types
pub let int4multirange_oid : Int = 4451

///|
pub let nummultirange_oid : Int = 4532

///|
pub let tsmultirange_oid : Int = 4533

///|
pub let tstzmultirange_oid : Int = 4534

///|
pub let datemultirange_oid : Int = 4535

///|
pub let int8multirange_oid : Int = 4536

///|
/// Array types
pub let bool_array_oid : Int = 1000

///|
pub let bytea_array_oid : Int = 1001

///|
pub let char_array_oid : Int = 1002

///|
pub let name_array_oid : Int = 1003

///|
pub let int2_array_oid : Int = 1005

///|
pub let int4_array_oid : Int = 1007

///|
pub let text_array_oid : Int = 1009

///|
pub let tid_array_oid : Int = 1010

///|
pub let xid_array_oid : Int = 1011

///|
pub let cid_array_oid : Int = 1012

///|
pub let bpchar_array_oid : Int = 1014

///|
pub let varchar_array_oid : Int = 1015

///|
pub let int8_array_oid : Int = 1016

///|
pub let point_array_oid : Int = 1017

///|
pub let lseg_array_oid : Int = 1018

///|
pub let path_array_oid : Int = 1019

///|
pub let box_array_oid : Int = 1020

///|
pub let float4_array_oid : Int = 1021

///|
pub let float8_array_oid : Int = 1022

///|
pub let polygon_array_oid : Int = 1027

///|
pub let oid_array_oid : Int = 1028

///|
pub let aclitem_array_oid : Int = 1034

///|
pub let macaddr_array_oid : Int = 1040

///|
pub let inet_array_oid : Int = 1041

///|
pub let date_array_oid : Int = 1182

///|
pub let time_array_oid : Int = 1183

///|
pub let timestamp_array_oid : Int = 1115

///|
pub let timestamptz_array_oid : Int = 1185

///|
pub let interval_array_oid : Int = 1187

///|
pub let numeric_array_oid : Int = 1231

///|
pub let timetz_array_oid : Int = 1270

///|
pub let bit_array_oid : Int = 1561

///|
pub let varbit_array_oid : Int = 1563

///|
pub let record_array_oid : Int = 2287

///|
pub let uuid_array_oid : Int = 2951

///|
pub let tsvector_array_oid : Int = 3643

///|
pub let json_array_oid : Int = 199

///|
pub let jsonb_array_oid : Int = 3807

///|
pub let xml_array_oid : Int = 143

///|
pub let jsonpath_array_oid : Int = 4073

///|
pub let xid8_array_oid : Int = 271

///|
pub let line_array_oid : Int = 629

///|
pub let circle_array_oid : Int = 719

///|
pub let daterange_array_oid : Int = 3913

///|
pub let int4range_array_oid : Int = 3905

///|
pub let numrange_array_oid : Int = 3907

///|
pub let tsrange_array_oid : Int = 3909

///|
pub let tstzrange_array_oid : Int = 3911

///|
pub let int8range_array_oid : Int = 3927

///|
pub let int4multirange_array_oid : Int = 6150

///|
pub let nummultirange_array_oid : Int = 6151

///|
pub let tsmultirange_array_oid : Int = 6152

///|
pub let tstzmultirange_array_oid : Int = 6153

///|
pub let datemultirange_array_oid : Int = 6155

///|
pub let int8multirange_array_oid : Int = 6157

// ---------------------------------------------------------------------------
// Default type map — built from the constants above
// ---------------------------------------------------------------------------

///|
/// Build the default type map with all common built-in PostgreSQL types.
fn build_default_map() -> TypeMap {
  TypeMap::new()
  // Base types
  .register("bool", bool_oid)
  .register("bytea", bytea_oid)
  .register("char", char_oid)
  .register("name", name_oid)
  .register("int8", int8_oid)
  .register("int2", int2_oid)
  .register("int4", int4_oid)
  .register("text", text_oid)
  .register("oid", oid_oid)
  .register("tid", tid_oid)
  .register("xid", xid_oid)
  .register("cid", cid_oid)
  .register("json", json_oid)
  .register("xml", xml_oid)
  .register("point", point_oid)
  .register("lseg", lseg_oid)
  .register("path", path_oid)
  .register("box", box_oid)
  .register("polygon", polygon_oid)
  .register("line", line_oid)
  .register("cidr", cidr_oid)
  .register("float4", float4_oid)
  .register("float8", float8_oid)
  .register("unknown", unknown_oid)
  .register("circle", circle_oid)
  .register("macaddr8", macaddr8_oid)
  .register("macaddr", macaddr_oid)
  .register("inet", inet_oid)
  .register("aclitem", aclitem_oid)
  .register("bpchar", bpchar_oid)
  .register("varchar", varchar_oid)
  .register("date", date_oid)
  .register("time", time_oid)
  .register("timestamp", timestamp_oid)
  .register("timestamptz", timestamptz_oid)
  .register("interval", interval_oid)
  .register("timetz", timetz_oid)
  .register("bit", bit_oid)
  .register("varbit", varbit_oid)
  .register("numeric", numeric_oid)
  .register("record", record_oid)
  .register("uuid", uuid_oid)
  .register("tsvector", tsvector_oid)
  .register("jsonb", jsonb_oid)
  .register("jsonpath", jsonpath_oid)
  .register("xid8", xid8_oid)
  // Range types
  .register("int4range", int4range_oid)
  .register("numrange", numrange_oid)
  .register("tsrange", tsrange_oid)
  .register("tstzrange", tstzrange_oid)
  .register("daterange", daterange_oid)
  .register("int8range", int8range_oid)
  // Multirange types
  .register("int4multirange", int4multirange_oid)
  .register("nummultirange", nummultirange_oid)
  .register("tsmultirange", tsmultirange_oid)
  .register("tstzmultirange", tstzmultirange_oid)
  .register("datemultirange", datemultirange_oid)
  .register("int8multirange", int8multirange_oid)
  // Array types
  .register("_bool", bool_array_oid)
  .register("_bytea", bytea_array_oid)
  .register("_char", char_array_oid)
  .register("_name", name_array_oid)
  .register("_int2", int2_array_oid)
  .register("_int4", int4_array_oid)
  .register("_text", text_array_oid)
  .register("_tid", tid_array_oid)
  .register("_xid", xid_array_oid)
  .register("_cid", cid_array_oid)
  .register("_bpchar", bpchar_array_oid)
  .register("_varchar", varchar_array_oid)
  .register("_int8", int8_array_oid)
  .register("_point", point_array_oid)
  .register("_lseg", lseg_array_oid)
  .register("_path", path_array_oid)
  .register("_box", box_array_oid)
  .register("_float4", float4_array_oid)
  .register("_float8", float8_array_oid)
  .register("_polygon", polygon_array_oid)
  .register("_oid", oid_array_oid)
  .register("_aclitem", aclitem_array_oid)
  .register("_macaddr", macaddr_array_oid)
  .register("_inet", inet_array_oid)
  .register("_date", date_array_oid)
  .register("_time", time_array_oid)
  .register("_timestamp", timestamp_array_oid)
  .register("_timestamptz", timestamptz_array_oid)
  .register("_interval", interval_array_oid)
  .register("_numeric", numeric_array_oid)
  .register("_timetz", timetz_array_oid)
  .register("_bit", bit_array_oid)
  .register("_varbit", varbit_array_oid)
  .register("_record", record_array_oid)
  .register("_uuid", uuid_array_oid)
  .register("_tsvector", tsvector_array_oid)
  .register("_json", json_array_oid)
  .register("_jsonb", jsonb_array_oid)
  .register("_xml", xml_array_oid)
  .register("_jsonpath", jsonpath_array_oid)
  .register("_xid8", xid8_array_oid)
  .register("_line", line_array_oid)
  .register("_circle", circle_array_oid)
  .register("_daterange", daterange_array_oid)
  .register("_int4range", int4range_array_oid)
  .register("_numrange", numrange_array_oid)
  .register("_tsrange", tsrange_array_oid)
  .register("_tstzrange", tstzrange_array_oid)
  .register("_int8range", int8range_array_oid)
  .register("_int4multirange", int4multirange_array_oid)
  .register("_nummultirange", nummultirange_array_oid)
  .register("_tsmultirange", tsmultirange_array_oid)
  .register("_tstzmultirange", tstzmultirange_array_oid)
  .register("_datemultirange", datemultirange_array_oid)
  .register("_int8multirange", int8multirange_array_oid)
  // information_schema.data_type aliases — separate from canonical PG names
  .register_data_type("integer", int4_oid)
  .register_data_type("bigint", int8_oid)
  .register_data_type("smallint", int2_oid)
  .register_data_type("boolean", bool_oid)
  .register_data_type("character varying", varchar_oid)
  .register_data_type("character", bpchar_oid)
  .register_data_type("double precision", float8_oid)
  .register_data_type("real", float4_oid)
  .register_data_type("timestamp with time zone", timestamptz_oid)
  .register_data_type("timestamp without time zone", timestamp_oid)
  .register_data_type("time with time zone", timetz_oid)
  .register_data_type("time without time zone", time_oid)
  .register_data_type("ARRAY", record_oid)
}

///|
/// Lazily-initialised default type map.
pub let default_map : TypeMap = build_default_map()

///|
/// Look up the human-readable type name for a PostgreSQL OID in the default map.
/// Returns `"unknown"` when the OID is not registered.
pub fn oid_to_name(oid : Int) -> String {
  match default_map.type_for_oid(oid) {
    Some(t) => t.name
    None => "unknown"
  }
}

///|
/// Look up the OID for a canonical PG type name. Returns `0` for unknown names.
pub fn name_to_oid(name : String) -> Int {
  match default_map.type_for_name(name) {
    Some(t) => t.oid
    None => 0
  }
}

///|
/// Look up a type by `information_schema.data_type` name in the default map.
/// Returns `None` for unknown names.
pub fn type_for_data_type(name : String) -> Type? {
  default_map.type_for_data_type(name)
}

///|
test "type_map_by_oid" {
  match default_map.type_for_oid(23) {
    Some(t) => {
      assert_eq(t.name, "int4")
      assert_eq(t.oid, 23)
    }
    None => fail("expected int4 type")
  }
  assert_true(default_map.type_for_oid(99999) is None)
}

///|
test "type_map_by_name" {
  match default_map.type_for_name("text") {
    Some(t) => assert_eq(t.oid, 25)
    None => fail("expected text type")
  }
}

///|
test "oid_to_name_common" {
  assert_eq(oid_to_name(23), "int4")
  assert_eq(oid_to_name(25), "text")
  assert_eq(oid_to_name(16), "bool")
  assert_eq(oid_to_name(701), "float8")
  assert_eq(oid_to_name(1700), "numeric")
  assert_eq(oid_to_name(3802), "jsonb")
  assert_eq(oid_to_name(99999), "unknown")
}

///|
test "oid_to_name_array" {
  assert_eq(oid_to_name(1007), "_int4")
  assert_eq(oid_to_name(1009), "_text")
  assert_eq(oid_to_name(1016), "_int8")
}

///|
test "oid_to_name_range" {
  assert_eq(oid_to_name(3904), "int4range")
  assert_eq(oid_to_name(3912), "daterange")
}

///|
test "information_schema_aliases" {
  // info schema names → Types
  match type_for_data_type("integer") {
    Some(t) => {
      assert_eq(t.name, "int4")
      assert_eq(t.oid, 23)
    }
    None => fail("expected integer type")
  }
  match type_for_data_type("character varying") {
    Some(t) => {
      assert_eq(t.name, "varchar")
      assert_eq(t.oid, 1043)
    }
    None => fail("expected character varying")
  }
  match type_for_data_type("timestamp with time zone") {
    Some(t) => {
      assert_eq(t.name, "timestamptz")
      assert_eq(t.oid, 1184)
    }
    None => fail("expected timestamptz")
  }
  // canonical PG names unchanged
  assert_eq(oid_to_name(23), "int4")
  assert_eq(oid_to_name(1184), "timestamptz")
  assert_eq(name_to_oid("int4"), 23)
  // unknown
  assert_true(type_for_data_type("nope") is None)
}