SV *
_create_class(pkg, name, ...)
    SV *pkg
    SV *name
  ALIAS:
    _create_class = METATYPE_CLASS
    _create_role  = METATYPE_ROLE
  CODE:
  {
    PERL_UNUSED_ARG(pkg);
    dKWARG(2);
    SV *superclassname = NULL;
    bool set_compclassmeta = false;
    bool set_abstract = false;

    {
      const COP *cop;

      const HV *mystash = CopSTASH(PL_curcop);

      for(int level = 0; level < 20; level++) {
        const PERL_CONTEXT *cx = caller_cx(level, NULL);
        if(!cx)
          break;

        cop = cx->blk_oldcop;

        if(CopSTASH(cop) != mystash)
          break;

        cop = NULL;
      }

      if(cop && !cophh_exists_pvs(CopHINTHASH_get(cop), "Object::Pad/experimental(mop)", 0))
        Perl_ck_warner(aTHX_ packWARN(WARN_EXPERIMENTAL),
          "The Object::Pad MOP API is experimental and may be changed or removed without notice");
    }

    static const char *args[] = {
      "extends",
      "isa",
      "_set_compclassmeta",
      "abstract",
      NULL
    };
    while(KWARG_NEXT(args)) {
      switch(kwarg) {
        case 0: /* extends */
        case 1: /* isa */
          if(ix != METATYPE_CLASS)
            croak("Only a class may extend another");
          superclassname = sv_mortalcopy(kwval);
          break;

        case 2: /* _set_compclassmeta */
          set_compclassmeta = SvTRUE(kwval);
          break;

        case 3: /* abstract */
          set_abstract = SvTRUE(kwval);
          break;
      }
    }

    ClassMeta *meta = mop_create_class(ix, name);

    if(superclassname && SvOK(superclassname))
      mop_class_set_superclass(meta, superclassname);

    if(set_abstract)
      meta->abstract = true;

    mop_class_begin(meta);

    RETVAL = newSV(0);
    sv_setref_uv(RETVAL, "Object::Pad::MOP::Class", PTR2UV(meta));

    if(set_compclassmeta) {
      compclassmeta_set(meta);

      CV *cv = newXS(NULL, &xsub_mop_class_seal, __FILE__);
      CvXSUBANY(cv).any_ptr = meta;

      if(!PL_unitcheckav)
        PL_unitcheckav = newAV();
      av_push(PL_unitcheckav, (SV *)cv);
    }
  }
  OUTPUT:
    RETVAL

bool
is_class(ClassMeta *self)
  ALIAS:
    is_class = METATYPE_CLASS
    is_role  = METATYPE_ROLE
  CODE:
    RETVAL = (self->type == ix);
  OUTPUT:
    RETVAL

bool
is_abstract(ClassMeta *self)
  CODE:
    RETVAL = self->abstract;
  OUTPUT:
    RETVAL

SV *
name(ClassMeta *self)
  CODE:
    RETVAL = SvREFCNT_inc(self->name);
  OUTPUT:
    RETVAL

void
superclasses(ClassMeta *self)
  PPCODE:
    if(self->type == METATYPE_CLASS && self->cls.supermeta) {
      PUSHs(sv_newmortal());
      sv_setref_uv(ST(0), "Object::Pad::MOP::Class", PTR2UV(self->cls.supermeta));
      XSRETURN(1);
    }

    XSRETURN(0);

void
direct_roles(ClassMeta *self)
  ALIAS:
    direct_roles = 0
    all_roles    = 1
  PPCODE:
  {
    U32 count = 0;

    /* TODO Consider recursion */
    U32 i;
    switch(self->type) {
      case METATYPE_CLASS: {
        U32 nroles;
        RoleEmbedding **embeddings = NULL;
        switch(ix) {
          case 0: embeddings = mop_class_get_direct_roles(self, &nroles); break;
          case 1: embeddings = mop_class_get_all_roles(self, &nroles); break;
        }

        for(i = 0; i < nroles; i++) {
          SV *sv = sv_newmortal();
          sv_setref_uv(sv, "Object::Pad::MOP::Class", PTR2UV(embeddings[i]->rolemeta));
          XPUSHs(sv);
          count++;
        }
        break;
      }

      case METATYPE_ROLE:
        break;
    }

    XSRETURN(count);
  }

void
add_role(ClassMeta *self, role)
    SV *role
  ALIAS:
    compose_role = 0
  CODE:
  {
    ClassMeta *rolemeta = NULL;

    PERL_UNUSED_VAR(ix);

    if(SvROK(role)) {
      if(!sv_derived_from(role, "Object::Pad::MOP::Class"))
        croak("Expected a role name string or Object::Pad::MOP::Class; got %" SVf, SVfARG(role));

      rolemeta = MUST_CLASSMETA_FROM_RV(role);
    }
    else {
      HV *rolestash = gv_stashsv(role, 0);
      /* Don't attempt to `require` it; that is caller's responsibilty */
      if(!rolestash)
        croak("Role %" SVf " does not exist", SVfARG(role));

      GV **metagvp = (GV **)hv_fetchs(rolestash, "META", 0);
      if(metagvp)
        rolemeta = NUM2PTR(ClassMeta *, SvUV(SvRV(GvSV(*metagvp))));
    }

    if(!rolemeta || rolemeta->type != METATYPE_ROLE)
      croak("%" SVf " is not a role", SVfARG(role));

    mop_class_begin(self);

    mop_class_add_role(self, rolemeta);
  }

void
add_BUILD(ClassMeta *self, code)
    CV *code
  CODE:
    mop_class_begin(self);

    mop_class_add_BUILD(self, (CV *)SvREFCNT_inc((SV *)code));

SV *
add_method(ClassMeta *self, mname, ...)
    SV *mname
  CODE:
  {
    if(items < 3)
      croak_xs_usage(cv, "self, mname, ..., code");
    SV *ref = ST(items-1); items--;
    if(!SvROK(ref) || SvTYPE(SvRV(ref)) != SVt_PVCV)
      croak("Expected CODE reference");

    dKWARG(2);

    /* Take a copy now to run FETCH magic */
    mname = sv_2mortal(newSVsv(mname));

    mop_class_begin(self);

    if(SvOK(mname) && SvPOK(mname) && strEQ(SvPVX(mname), "BUILD")) {
      croak("Adding a method called BUILD is not supported; use ->add_BUILD directly");
    }

    MethodMeta *methodmeta = mop_class_add_method_cv(self, mname, (CV *)SvREFCNT_inc(CV_FROM_REF(ref)));

    static const char *args[] = {
      "common",
      NULL
    };
    while(KWARG_NEXT(args)) {
      switch(kwarg) {
        case 0: /* common */
          methodmeta->is_common = SvTRUE(kwval);
          break;
      }
    }

    RETVAL = newSV(0);
    sv_setref_uv(RETVAL, "Object::Pad::MOP::Method", PTR2UV(methodmeta));
  }
  OUTPUT:
    RETVAL

void
get_direct_method(ClassMeta *self, methodname)
    SV *methodname
  ALIAS:
    get_method = 1
  PPCODE:
  {
    bool recurse = !!ix;
    ClassMeta *class = self;

    do {
      AV *methods = class->direct_methods;
      U32 nmethods = av_count(methods);

      U32 i;
      for(i = 0; i < nmethods; i++) {
        MethodMeta *methodmeta = (MethodMeta *)AvARRAY(methods)[i];

        if(!sv_eq(methodmeta->name, methodname))
          continue;

        ST(0) = sv_newmortal();
        sv_setref_iv(ST(0), "Object::Pad::MOP::Method", PTR2UV(methodmeta));
        XSRETURN(1);
      }

      if(class->type == METATYPE_CLASS)
        class = class->cls.supermeta;
      else
        class = NULL;
    } while(recurse && class);

    croak("Class %" SVf " does not have a method called '%" SVf "'",
      SVfARG(self->name), SVfARG(methodname));
  }

void
direct_methods(ClassMeta *self)
  ALIAS:
    all_methods = 1
  PPCODE:
    bool recurse = !!ix;

    /* A hash to remove overrides */
    HV *mnames = NULL;
    if(recurse) {
      mnames = newHV();
      SAVEFREESV(mnames);
    }

    U32 retcount = 0;

    do {
      AV *methods = self->direct_methods;
      U32 nmethods = av_count(methods);

      EXTEND(SP, retcount + nmethods);  /* might be an overestimate but don't worry */
      U32 i;
      for(i = 0; i < nmethods; i++) {
        MethodMeta *methodmeta = (MethodMeta *)AvARRAY(methods)[i];

        if(mnames && hv_exists_ent(mnames, methodmeta->name, 0))
          continue;

        ST(retcount) = sv_newmortal();
        sv_setref_iv(ST(retcount), "Object::Pad::MOP::Method", PTR2UV(methodmeta));
        retcount++;

        hv_store_ent(mnames, methodmeta->name, &PL_sv_yes, 0);
      }

      if(self->type == METATYPE_CLASS)
        self = self->cls.supermeta;
      else
        self = NULL;
    } while(recurse && self);

    XSRETURN(retcount);

void
add_required_method(ClassMeta *self, mname)
    SV *mname
  CODE:
    mop_class_begin(self);

    mop_class_add_required_method(self, mname);

SV *
add_field(ClassMeta *self, fieldname, ...)
    SV *fieldname
  CODE:
  {
    dKWARG(2);

    mop_class_begin(self);

    FieldMeta *fieldmeta = mop_class_add_field(self, sv_mortalcopy(fieldname));

    static const char *args[] = {
      "default",
      "param",
      "reader",
      "writer",
      "mutator",
      "accessor",
      "weak",
      "attributes",
      NULL,
    };
    while(KWARG_NEXT(args)) {
      switch(kwarg) {
        case 0: /* default */
          mop_field_set_default_sv(fieldmeta, newSVsv(kwval));
          break;

        case 1: /* param */
          mop_field_apply_attribute(fieldmeta, "param", kwval);
          break;

        case 2: /* reader */
          mop_field_apply_attribute(fieldmeta, "reader", kwval);
          break;

        case 3: /* writer */
          mop_field_apply_attribute(fieldmeta, "writer", kwval);
          break;

        case 4: /* mutator */
          mop_field_apply_attribute(fieldmeta, "mutator", kwval);
          break;

        case 5: /* accessor */
          mop_field_apply_attribute(fieldmeta, "accessor", kwval);
          break;

        case 6: /* weak */
          mop_field_apply_attribute(fieldmeta, "weak", NULL);
          break;

        case 7: /* attributes */
        {
          if(!SvROK(kwval) || SvTYPE(SvRV(kwval)) != SVt_PVAV)
            croak("Expected 'attributes' to be given an ARRAY reference");
          AV *attributes = AV_FROM_REF(kwval);
          for(IV ix = 0; ix < av_count(attributes); ix += 2) {
            SV *attrname = *av_fetch(attributes, ix, 0);
            SV *attrval  = ix+1 < av_count(attributes) ? *av_fetch(attributes, ix + 1, 0) : &PL_sv_undef;

            mop_field_apply_attribute(fieldmeta, SvPV_nolen(attrname), attrval);
          }
        }
      }
    }

    mop_field_seal(fieldmeta);

    RETVAL = newSV(0);
    sv_setref_uv(RETVAL, "Object::Pad::MOP::Field", PTR2UV(fieldmeta));
  }
  OUTPUT:
    RETVAL

void
get_field(ClassMeta *self, fieldname)
    SV *fieldname
  PPCODE:
  {
    FieldMeta *fieldmeta = mop_class_find_field(self, fieldname, FIND_FIELD_ONLY_DIRECT);
    if(fieldmeta) {
      ST(0) = sv_newmortal();
      sv_setref_iv(ST(0), "Object::Pad::MOP::Field", PTR2UV(fieldmeta));
      XSRETURN(1);
    }

    croak("Class %" SVf " does not have a field called '%" SVf "'",
      self->name, fieldname);
  }

void
fields(ClassMeta *self)
  PPCODE:
    AV *fields = self->fields;
    U32 nfields = av_count(fields);

    EXTEND(SP, nfields);

    U32 retcount = 0;
    FIELDOFFSET i;
    for(i = 0; i < nfields; i++) {
      FieldMeta *fieldmeta = (FieldMeta *)AvARRAY(fields)[i];
      if(!fieldmeta->is_direct)
        continue;

      ST(i) = sv_newmortal();
      sv_setref_iv(ST(i), "Object::Pad::MOP::Field", PTR2UV(fieldmeta));
      retcount++;
    }
    XSRETURN(retcount);

void
required_method_names(ClassMeta *self)
  PPCODE:
    if(self->type != METATYPE_ROLE)
      croak("Can only call ->required_method_names on a metaclass for a role");

    AV *required_methods = self->requiremethods;
    U32 nmethods = av_count(required_methods);

    EXTEND(SP, nmethods);

    int i;
    for(i = 0; i < nmethods; i++) {
      ST(i) = sv_2mortal(newSVsv(AvARRAY(required_methods)[i]));
    }
    XSRETURN(nmethods);

void
seal(ClassMeta *self)
  CODE:
    mop_class_seal(self);