///|
struct Any {
  id : TypeId
  val : Payload
  debug : &Debug
}

///|
pub extenum Payload {}

///|
pub(open) trait Anyable: Debug {
  fn iso() -> Iso[Self]
}

///|
pub(all) struct Iso[T] {
  id : TypeId
  box : (T) -> Payload
  unbox : (Payload) -> T
}

///|
pub impl Debug for Any with fn to_repr(self) {
  self.debug.to_repr()
}

///|
pub fn[T : Anyable] Any::Any(v : T) -> Self {
  let { id, box, .. } : Iso[T] = Anyable::iso()
  { id, val: box(v), debug: v }
}

///|
pub fn Any::id(self : Self) -> TypeId {
  self.id
}

///|
pub struct TypeId {
  pkg : String
  ctor : String
  generics : Array[TypeId]
} derive(Debug, Eq, Compare)

///|
pub fn TypeId::TypeId(
  pkg : String,
  ctor : String,
  generics : Array[TypeId],
) -> TypeId {
  { pkg, ctor, generics }
}

///|
pub impl Show for TypeId with fn output(self, buf) {
  if self.pkg != "" {
    buf <+ "@\{self.pkg}."
  }
  buf <+ "\{self.ctor}"
  match self.generics {
    [] => ()
    [x] => buf <+ "[\{x}]"
    [x, .. xs] => {
      buf <+ "[\{x},"
      for x in xs {
        buf <+ "\{x},"
      }
      buf <+ "]"
    }
  }
}

///|
#callsite(autofill(loc))
pub fn[T : Anyable] Any::dyn_cast(x : Any, loc~ : SourceLoc) -> T raise {
  let { id, unbox, .. } : Iso[T] = Anyable::iso()
  if id == x.id {
    unbox(x.val)
  } else {
    fail("failed to cast \{x.id} to \{id}", loc~)
  }
}

///|
pub fn[T : Anyable] Any::unsafe_cast(x : Any) -> T {
  let { unbox, .. } : Iso[T] = Anyable::iso()
  unbox(x.val)
}