#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "object_compat.h"

/* Object flags - stored in mg_private */
#define OBJ_FLAG_LOCKED  0x01
#define OBJ_FLAG_FROZEN  0x02

/* Custom op definitions */
static XOP object_new_xop;
static XOP object_get_xop;
static XOP object_set_xop;

/* Per-class metadata */
typedef struct {
    char *class_name;
    HV *prop_to_idx;      /* property name -> slot index */
    char **idx_to_prop;   /* slot index -> property name */
    IV slot_count;
    HV *stash;            /* cached stash pointer */
} ClassMeta;

/* Global class registry */
static HV *g_class_registry = NULL;  /* class name -> ClassMeta* */

/* Forward declarations */
static ClassMeta* get_class_meta(pTHX_ const char *class_name, STRLEN len);
static void install_constructor(pTHX_ const char *class_name, ClassMeta *meta);
static void install_accessor(pTHX_ const char *class_name, const char *prop_name, IV idx);

/* Magic vtable for object flags */
static MGVTBL object_magic_vtbl = {
    NULL,  /* get */
    NULL,  /* set */
    NULL,  /* len */
    NULL,  /* clear */
    NULL,  /* free */
    NULL,  /* copy */
    NULL,  /* dup */
    NULL   /* local */
};

/* Get object magic (for flags) */
static MAGIC* get_object_magic(pTHX_ SV *obj) {
    MAGIC *mg;
    if (!SvROK(obj)) return NULL;
    mg = mg_find(SvRV(obj), PERL_MAGIC_ext);
    while (mg) {
        if (mg->mg_virtual == &object_magic_vtbl) return mg;
        mg = mg->mg_moremagic;
    }
    return NULL;
}

/* Add object magic */
static MAGIC* add_object_magic(pTHX_ SV *obj) {
    MAGIC *mg;
    SV *rv = SvRV(obj);
    mg = sv_magicext(rv, NULL, PERL_MAGIC_ext, &object_magic_vtbl, NULL, 0);
    mg->mg_private = 0;  /* flags */
    return mg;
}

/* ============================================
   Class definition and registration
   ============================================ */

static ClassMeta* create_class_meta(pTHX_ const char *class_name, STRLEN len) {
    ClassMeta *meta;
    Newxz(meta, 1, ClassMeta);
    Newxz(meta->class_name, len + 1, char);
    Copy(class_name, meta->class_name, len, char);
    meta->class_name[len] = '\0';
    meta->prop_to_idx = newHV();
    meta->idx_to_prop = NULL;
    meta->slot_count = 1;  /* slot 0 reserved for prototype */
    meta->stash = gv_stashpvn(class_name, len, GV_ADD);
    return meta;
}

static ClassMeta* get_class_meta(pTHX_ const char *class_name, STRLEN len) {
    SV **svp;
    if (!g_class_registry) return NULL;
    svp = hv_fetch(g_class_registry, class_name, len, 0);
    if (svp && SvIOK(*svp)) {
        return INT2PTR(ClassMeta*, SvIV(*svp));
    }
    return NULL;
}

static void register_class_meta(pTHX_ const char *class_name, STRLEN len, ClassMeta *meta) {
    if (!g_class_registry) {
        g_class_registry = newHV();
    }
    hv_store(g_class_registry, class_name, len, newSViv(PTR2IV(meta)), 0);
}

/* ============================================
   Custom OP: object constructor
   ============================================ */

/* pp_object_new - create new object, class info in op_targ, args on stack */
static OP* pp_object_new(pTHX) {
    dSP; dMARK;
    IV items = SP - MARK;
    ClassMeta *meta = INT2PTR(ClassMeta*, PL_op->op_targ);
    AV *obj_av;
    SV *obj_sv;
    IV i;
    U32 is_named = PL_op->op_private;  /* 1 = named pairs, 0 = positional */

    /* Create array with pre-extended size */
    obj_av = newAV();
    av_extend(obj_av, meta->slot_count - 1);

    /* Slot 0 = prototype (initially undef) */
    av_store(obj_av, 0, &PL_sv_undef);

    if (is_named) {
        /* Named pairs: name => value, name => value */
        for (i = 0; i < items; i += 2) {
            SV *key_sv = MARK[i + 1];
            SV *val_sv = (i + 1 < items) ? MARK[i + 2] : &PL_sv_undef;
            STRLEN key_len;
            const char *key = SvPV(key_sv, key_len);
            SV **idx_svp = hv_fetch(meta->prop_to_idx, key, key_len, 0);
            if (idx_svp && SvIOK(*idx_svp)) {
                IV idx = SvIV(*idx_svp);
                av_store(obj_av, idx, newSVsv(val_sv));
            }
        }
    } else {
        /* Positional: value, value, value */
        for (i = 0; i < items && i < meta->slot_count - 1; i++) {
            av_store(obj_av, i + 1, newSVsv(MARK[i + 1]));
        }
    }

    /* Fill any unset slots with undef */
    for (i = 1; i < meta->slot_count; i++) {
        if (!av_fetch(obj_av, i, 0)) {
            av_store(obj_av, i, newSV(0));
        }
    }

    /* Create blessed reference */
    obj_sv = newRV_noinc((SV*)obj_av);
    sv_bless(obj_sv, meta->stash);

    /* Add object magic for flags */
    add_object_magic(aTHX_ obj_sv);

    SP = MARK;
    XPUSHs(obj_sv);
    PUTBACK;
    return NORMAL;
}

/* ============================================
   Custom OP: property accessor (get)
   ============================================ */

static OP* pp_object_get(pTHX) {
    dSP;
    SV *obj = TOPs;
    IV idx = PL_op->op_targ;
    AV *av;
    SV **svp;

    if (!SvROK(obj) || SvTYPE(SvRV(obj)) != SVt_PVAV) {
        croak("Not an object");
    }

    av = (AV*)SvRV(obj);
    svp = av_fetch(av, idx, 0);
    
    if (svp && SvOK(*svp)) {
        SETs(*svp);
    } else {
        /* Check prototype chain */
        SV **proto_svp = av_fetch(av, 0, 0);
        if (proto_svp && SvROK(*proto_svp)) {
            /* Recurse into prototype - for now, simple one-level */
            AV *proto_av = (AV*)SvRV(*proto_svp);
            svp = av_fetch(proto_av, idx, 0);
            if (svp && SvOK(*svp)) {
                SETs(*svp);
                RETURN;
            }
        }
        SETs(&PL_sv_undef);
    }
    RETURN;
}

/* ============================================
   Custom OP: property accessor (set)
   ============================================ */

static OP* pp_object_set(pTHX) {
    dSP;
    SV *val = POPs;
    SV *obj = TOPs;
    IV idx = PL_op->op_targ;
    AV *av;
    MAGIC *mg;

    if (!SvROK(obj) || SvTYPE(SvRV(obj)) != SVt_PVAV) {
        croak("Not an object");
    }

    av = (AV*)SvRV(obj);

    /* Check frozen/locked */
    mg = get_object_magic(aTHX_ obj);
    if (mg && (mg->mg_private & OBJ_FLAG_FROZEN)) {
        croak("Cannot modify frozen object");
    }

    av_store(av, idx, newSVsv(val));
    SETs(val);
    RETURN;
}

/* ============================================
   Call checker for accessor
   ============================================ */

static OP* accessor_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
    IV idx = SvIV(ckobj);
    OP *pushop, *cvop, *selfop, *argop;
    OP *newop;

    PERL_UNUSED_ARG(namegv);

    pushop = cUNOPx(entersubop)->op_first;
    if (!OpHAS_SIBLING(pushop)) {
        pushop = cUNOPx(pushop)->op_first;
    }

    selfop = OpSIBLING(pushop);
    cvop = selfop;
    argop = selfop;
    while (OpHAS_SIBLING(cvop)) {
        argop = cvop;
        cvop = OpSIBLING(cvop);
    }

    /* Check if there's an argument after self (setter call) */
    if (argop != selfop) {
        /* Setter: $obj->name($value) */
        OP *valop = OpSIBLING(selfop);
        
        /* Detach self and val */
        OpMORESIB_set(pushop, cvop);
        OpLASTSIB_set(valop, NULL);
        OpLASTSIB_set(selfop, NULL);
        
        /* Create binop with self and val */
        newop = newBINOP(OP_CUSTOM, 0, selfop, valop);
        newop->op_ppaddr = pp_object_set;
        newop->op_targ = idx;
        
        op_free(entersubop);
        return newop;
    } else {
        /* Getter: $obj->name */
        OpMORESIB_set(pushop, cvop);
        OpLASTSIB_set(selfop, NULL);
        
        newop = newUNOP(OP_CUSTOM, 0, selfop);
        newop->op_ppaddr = pp_object_get;
        newop->op_targ = idx;
        
        op_free(entersubop);
        return newop;
    }
}

/* ============================================
   XS Fallback functions
   ============================================ */

/* XS fallback for new (when call checker can't optimize) */
static XS(xs_object_new_fallback) {
    dXSARGS;
    ClassMeta *meta = INT2PTR(ClassMeta*, CvXSUBANY(cv).any_iv);
    AV *obj_av;
    SV *obj_sv;
    IV i;
    IV start_arg = 0;
    IV arg_count;
    int is_named = 0;

    /* Skip class name if passed as invocant (Cat->new or new Cat) */
    if (items > 0 && SvPOK(ST(0)) && !SvROK(ST(0))) {
        STRLEN len;
        const char *pv = SvPV(ST(0), len);
        if (strEQ(pv, meta->class_name)) {
            start_arg = 1;
        }
    }

    arg_count = items - start_arg;

    /* Detect named pairs: even count and first arg is a known property name */
    if (arg_count > 0 && (arg_count % 2) == 0 && SvPOK(ST(start_arg))) {
        STRLEN len;
        const char *pv = SvPV(ST(start_arg), len);
        if (hv_exists(meta->prop_to_idx, pv, len)) {
            is_named = 1;
        }
    }

    obj_av = newAV();
    av_extend(obj_av, meta->slot_count - 1);
    av_store(obj_av, 0, &PL_sv_undef);

    if (is_named) {
        /* Named pairs: name => value, name => value */
        for (i = start_arg; i < items; i += 2) {
            SV *key_sv = ST(i);
            SV *val_sv = (i + 1 < items) ? ST(i + 1) : &PL_sv_undef;
            STRLEN key_len;
            const char *key = SvPV(key_sv, key_len);
            SV **idx_svp = hv_fetch(meta->prop_to_idx, key, key_len, 0);
            if (idx_svp && SvIOK(*idx_svp)) {
                IV idx = SvIV(*idx_svp);
                av_store(obj_av, idx, newSVsv(val_sv));
            }
        }
    } else {
        /* Positional fallback - skip class name if present */
        for (i = start_arg; i < items && (i - start_arg) < meta->slot_count - 1; i++) {
            av_store(obj_av, (i - start_arg) + 1, newSVsv(ST(i)));
        }
    }

    /* Fill any unset slots with undef */
    for (i = 1; i < meta->slot_count; i++) {
        if (!av_fetch(obj_av, i, 0)) {
            av_store(obj_av, i, newSV(0));
        }
    }

    obj_sv = newRV_noinc((SV*)obj_av);
    sv_bless(obj_sv, meta->stash);
    add_object_magic(aTHX_ obj_sv);

    ST(0) = obj_sv;
    XSRETURN(1);
}

/* XS fallback accessor */
static XS(xs_accessor_fallback) {
    dXSARGS;
    IV idx = CvXSUBANY(cv).any_iv;
    SV *self = ST(0);
    AV *av;

    if (!SvROK(self) || SvTYPE(SvRV(self)) != SVt_PVAV) {
        croak("Not an object");
    }
    av = (AV*)SvRV(self);

    if (items > 1) {
        /* Setter */
        MAGIC *mg = get_object_magic(aTHX_ self);
        if (mg && (mg->mg_private & OBJ_FLAG_FROZEN)) {
            croak("Cannot modify frozen object");
        }
        av_store(av, idx, newSVsv(ST(1)));
        ST(0) = ST(1);
        XSRETURN(1);
    } else {
        /* Getter */
        SV **svp = av_fetch(av, idx, 0);
        if (svp && SvOK(*svp)) {
            ST(0) = *svp;
        } else {
            ST(0) = &PL_sv_undef;
        }
        XSRETURN(1);
    }
}

/* ============================================
   Install constructor into class
   ============================================ */

static void install_constructor(pTHX_ const char *class_name, ClassMeta *meta) {
    char full_name[256];
    CV *cv;

    snprintf(full_name, sizeof(full_name), "%s::new", class_name);
    
    /* Create a minimal CV that will be replaced by call checker */
    cv = newXS(full_name, xs_object_new_fallback, __FILE__);
    CvXSUBANY(cv).any_iv = PTR2IV(meta);
}

/* ============================================
   Custom OP: fast function-style getter
   op_targ = idx, reads object from stack
   ============================================ */
static XOP object_func_get_xop;
static XOP object_func_set_xop;

static OP* pp_object_func_get(pTHX) {
    dSP;
    SV *obj = TOPs;  /* peek, don't pop */
    IV idx = PL_op->op_targ;
    AV *av = (AV*)SvRV(obj);  /* Skip check - trust call checker */
    SV **svp = AvARRAY(av) + idx;  /* Direct array access */
    SETs((*svp && SvOK(*svp)) ? *svp : &PL_sv_undef);
    RETURN;
}

static OP* pp_object_func_set(pTHX) {
    dSP;
    SV *val = TOPs;  /* value on top */
    SV *obj = TOPm1s;  /* object below */
    IV idx = PL_op->op_targ;
    AV *av = (AV*)SvRV(obj);  /* Skip check - trust call checker */
    SV **slot = AvARRAY(av) + idx;
    SV *old = *slot;
    
    *slot = SvREFCNT_inc_simple_NN(val);  /* Inc refcount in place */
    if (old) SvREFCNT_dec(old);
    
    TOPm1s = val;  /* Replace object with value */
    SP--;          /* Pop top, leaving value */
    RETURN;
}

/* Call checker for function-style accessor: name($obj) or name($obj, $val) */
static OP* func_accessor_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
    IV idx = SvIV(ckobj);
    OP *pushop, *cvop, *objop, *valop;
    OP *newop;

    PERL_UNUSED_ARG(namegv);

    pushop = cUNOPx(entersubop)->op_first;
    if (!OpHAS_SIBLING(pushop)) {
        pushop = cUNOPx(pushop)->op_first;
    }

    objop = OpSIBLING(pushop);
    cvop = objop;
    valop = NULL;
    
    while (OpHAS_SIBLING(cvop)) {
        valop = cvop;
        cvop = OpSIBLING(cvop);
    }

    if (valop && valop != objop) {
        /* Setter: name($obj, $val) */
        valop = OpSIBLING(objop);
        OpMORESIB_set(pushop, cvop);
        OpLASTSIB_set(valop, NULL);
        OpLASTSIB_set(objop, NULL);
        
        newop = newBINOP(OP_CUSTOM, 0, objop, valop);
        newop->op_ppaddr = pp_object_func_set;
        newop->op_targ = idx;
    } else {
        /* Getter: name($obj) */
        OpMORESIB_set(pushop, cvop);
        OpLASTSIB_set(objop, NULL);
        
        newop = newUNOP(OP_CUSTOM, 0, objop);
        newop->op_ppaddr = pp_object_func_get;
        newop->op_targ = idx;
    }
    
    op_free(entersubop);
    return newop;
}

/* XS fallback for function-style accessor */
static XS(xs_func_accessor_fallback) {
    dXSARGS;
    IV idx = CvXSUBANY(cv).any_iv;
    SV *obj = ST(0);
    AV *av;

    if (!SvROK(obj) || SvTYPE(SvRV(obj)) != SVt_PVAV) {
        croak("Not an object");
    }
    av = (AV*)SvRV(obj);

    if (items > 1) {
        av_store(av, idx, newSVsv(ST(1)));
        ST(0) = ST(1);
    } else {
        SV **svp = av_fetch(av, idx, 0);
        ST(0) = (svp && SvOK(*svp)) ? *svp : &PL_sv_undef;
    }
    XSRETURN(1);
}

/* Install function-style accessor in caller's namespace */
static void install_func_accessor(pTHX_ const char *pkg, const char *prop_name, IV idx) {
    char full_name[256];
    CV *cv;
    SV *ckobj;

    snprintf(full_name, sizeof(full_name), "%s::%s", pkg, prop_name);
    
    cv = newXS(full_name, xs_func_accessor_fallback, __FILE__);
    CvXSUBANY(cv).any_iv = idx;
    
    ckobj = newSViv(idx);
    cv_set_call_checker(cv, func_accessor_call_checker, ckobj);
}

/* object::import_accessors("Class", "targetpkg") - import fast accessors */
static XS(xs_import_accessors) {
    dXSARGS;
    STRLEN class_len, pkg_len;
    const char *class_pv, *pkg_pv;
    ClassMeta *meta;
    IV i;
    
    if (items < 1) croak("Usage: object::import_accessors($class [, $package])");
    
    class_pv = SvPV(ST(0), class_len);
    
    if (items > 1) {
        pkg_pv = SvPV(ST(1), pkg_len);
    } else {
        /* Default to caller's package */
        pkg_pv = CopSTASHPV(PL_curcop);
    }
    
    meta = get_class_meta(aTHX_ class_pv, class_len);
    if (!meta) {
        croak("Class '%s' not defined with object::define", class_pv);
    }
    
    /* Install function-style accessors for each property */
    for (i = 1; i < meta->slot_count; i++) {
        if (meta->idx_to_prop[i]) {
            install_func_accessor(aTHX_ pkg_pv, meta->idx_to_prop[i], i);
        }
    }
    
    XSRETURN_EMPTY;
}

/* object::import_accessor("Class", "prop", "alias") - import single accessor with alias */
static XS(xs_import_accessor) {
    dXSARGS;
    STRLEN class_len, prop_len, alias_len, pkg_len;
    const char *class_pv, *prop_pv, *alias_pv, *pkg_pv;
    ClassMeta *meta;
    SV **idx_svp;
    IV idx;
    
    if (items < 2) croak("Usage: object::import_accessor($class, $prop [, $alias [, $package]])");
    
    class_pv = SvPV(ST(0), class_len);
    prop_pv = SvPV(ST(1), prop_len);
    
    /* Alias defaults to property name */
    if (items > 2 && SvOK(ST(2))) {
        alias_pv = SvPV(ST(2), alias_len);
    } else {
        alias_pv = prop_pv;
    }
    
    /* Package defaults to caller */
    if (items > 3) {
        pkg_pv = SvPV(ST(3), pkg_len);
    } else {
        pkg_pv = CopSTASHPV(PL_curcop);
    }
    
    meta = get_class_meta(aTHX_ class_pv, class_len);
    if (!meta) {
        croak("Class '%s' not defined with object::define", class_pv);
    }
    
    /* Look up property index */
    idx_svp = hv_fetch(meta->prop_to_idx, prop_pv, prop_len, 0);
    if (!idx_svp) {
        croak("Property '%s' not defined in class '%s'", prop_pv, class_pv);
    }
    idx = SvIV(*idx_svp);
    
    /* Install with alias name */
    install_func_accessor(aTHX_ pkg_pv, alias_pv, idx);
    
    XSRETURN_EMPTY;
}

/* ============================================
   Install accessor into class
   ============================================ */

static void install_accessor(pTHX_ const char *class_name, const char *prop_name, IV idx) {
    char full_name[256];
    CV *cv;
    SV *ckobj;

    snprintf(full_name, sizeof(full_name), "%s::%s", class_name, prop_name);
    
    cv = newXS(full_name, xs_accessor_fallback, __FILE__);
    CvXSUBANY(cv).any_iv = idx;
    
    ckobj = newSViv(idx);
    cv_set_call_checker(cv, accessor_call_checker, ckobj);
}

/* ============================================
   XS API Functions
   ============================================ */

static XS(xs_define) {
    dXSARGS;
    STRLEN class_len;
    const char *class_pv;
    ClassMeta *meta;
    IV i;
    
    if (items < 1) croak("Usage: object::define($class, @properties)");
    
    class_pv = SvPV(ST(0), class_len);

    /* Get or create class meta */
    meta = get_class_meta(aTHX_ class_pv, class_len);
    if (!meta) {
        meta = create_class_meta(aTHX_ class_pv, class_len);
        register_class_meta(aTHX_ class_pv, class_len, meta);
    }

    /* Allocate property name array */
    Renew(meta->idx_to_prop, items, char*);

    /* Register each property */
    for (i = 1; i < items; i++) {
        STRLEN prop_len;
        const char *prop_pv = SvPV(ST(i), prop_len);
        IV idx = meta->slot_count++;
        char *prop_copy;

        /* Store name -> idx mapping */
        hv_store(meta->prop_to_idx, prop_pv, prop_len, newSViv(idx), 0);

        /* Store idx -> name mapping */
        Newx(prop_copy, prop_len + 1, char);
        Copy(prop_pv, prop_copy, prop_len, char);
        prop_copy[prop_len] = '\0';
        meta->idx_to_prop[idx] = prop_copy;

        /* Install accessor method */
        install_accessor(aTHX_ class_pv, prop_pv, idx);
    }

    /* Install constructor */
    install_constructor(aTHX_ class_pv, meta);
    
    XSRETURN_EMPTY;
}

static XS(xs_prototype) {
    dXSARGS;
    AV *av;
    SV **svp;
    
    if (items < 1) croak("Usage: object::prototype($obj)");
    
    if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVAV) {
        croak("Not an object");
    }
    av = (AV*)SvRV(ST(0));
    svp = av_fetch(av, 0, 0);
    if (svp && SvOK(*svp)) {
        ST(0) = SvREFCNT_inc(*svp);
    } else {
        ST(0) = &PL_sv_undef;
    }
    XSRETURN(1);
}

static XS(xs_set_prototype) {
    dXSARGS;
    AV *av;
    MAGIC *mg;
    
    if (items < 2) croak("Usage: object::set_prototype($obj, $proto)");
    
    if (!SvROK(ST(0)) || SvTYPE(SvRV(ST(0))) != SVt_PVAV) {
        croak("Not an object");
    }
    av = (AV*)SvRV(ST(0));

    mg = get_object_magic(aTHX_ ST(0));
    if (mg && (mg->mg_private & OBJ_FLAG_FROZEN)) {
        croak("Cannot modify frozen object");
    }

    av_store(av, 0, newSVsv(ST(1)));
    XSRETURN_EMPTY;
}

static XS(xs_lock) {
    dXSARGS;
    MAGIC *mg;
    
    if (items < 1) croak("Usage: object::lock($obj)");
    
    mg = get_object_magic(aTHX_ ST(0));
    if (!mg) mg = add_object_magic(aTHX_ ST(0));
    if (mg->mg_private & OBJ_FLAG_FROZEN) {
        croak("Object is frozen");
    }
    mg->mg_private |= OBJ_FLAG_LOCKED;
    XSRETURN_EMPTY;
}

static XS(xs_unlock) {
    dXSARGS;
    MAGIC *mg;
    
    if (items < 1) croak("Usage: object::unlock($obj)");
    
    mg = get_object_magic(aTHX_ ST(0));
    if (mg) {
        if (mg->mg_private & OBJ_FLAG_FROZEN) {
            croak("Cannot unlock frozen object");
        }
        mg->mg_private &= ~OBJ_FLAG_LOCKED;
    }
    XSRETURN_EMPTY;
}

static XS(xs_freeze) {
    dXSARGS;
    MAGIC *mg;
    
    if (items < 1) croak("Usage: object::freeze($obj)");
    
    mg = get_object_magic(aTHX_ ST(0));
    if (!mg) mg = add_object_magic(aTHX_ ST(0));
    mg->mg_private |= (OBJ_FLAG_FROZEN | OBJ_FLAG_LOCKED);
    XSRETURN_EMPTY;
}

static XS(xs_is_frozen) {
    dXSARGS;
    MAGIC *mg;
    
    if (items < 1) croak("Usage: object::is_frozen($obj)");
    
    mg = get_object_magic(aTHX_ ST(0));
    if (mg && (mg->mg_private & OBJ_FLAG_FROZEN)) {
        XSRETURN_YES;
    }
    XSRETURN_NO;
}

static XS(xs_is_locked) {
    dXSARGS;
    MAGIC *mg;
    
    if (items < 1) croak("Usage: object::is_locked($obj)");
    
    mg = get_object_magic(aTHX_ ST(0));
    if (mg && (mg->mg_private & OBJ_FLAG_LOCKED)) {
        XSRETURN_YES;
    }
    XSRETURN_NO;
}

/* ============================================
   Boot
   ============================================ */

XS_EXTERNAL(boot_object) {
    dXSBOOTARGSXSAPIVERCHK;
    PERL_UNUSED_VAR(items);

    /* Register custom ops */
    XopENTRY_set(&object_new_xop, xop_name, "object_new");
    XopENTRY_set(&object_new_xop, xop_desc, "object constructor");
    Perl_custom_op_register(aTHX_ pp_object_new, &object_new_xop);
    
    XopENTRY_set(&object_get_xop, xop_name, "object_get");
    XopENTRY_set(&object_get_xop, xop_desc, "object property get");
    Perl_custom_op_register(aTHX_ pp_object_get, &object_get_xop);
    
    XopENTRY_set(&object_set_xop, xop_name, "object_set");
    XopENTRY_set(&object_set_xop, xop_desc, "object property set");
    Perl_custom_op_register(aTHX_ pp_object_set, &object_set_xop);

    XopENTRY_set(&object_func_get_xop, xop_name, "object_func_get");
    XopENTRY_set(&object_func_get_xop, xop_desc, "object function-style get");
    Perl_custom_op_register(aTHX_ pp_object_func_get, &object_func_get_xop);
    
    XopENTRY_set(&object_func_set_xop, xop_name, "object_func_set");
    XopENTRY_set(&object_func_set_xop, xop_desc, "object function-style set");
    Perl_custom_op_register(aTHX_ pp_object_func_set, &object_func_set_xop);

    /* Initialize class registry */
    g_class_registry = newHV();

    /* Install XS functions */
    newXS("object::define", xs_define, __FILE__);
    newXS("object::import_accessors", xs_import_accessors, __FILE__);
    newXS("object::import_accessor", xs_import_accessor, __FILE__);
    newXS("object::prototype", xs_prototype, __FILE__);
    newXS("object::set_prototype", xs_set_prototype, __FILE__);
    newXS("object::lock", xs_lock, __FILE__);
    newXS("object::unlock", xs_unlock, __FILE__);
    newXS("object::freeze", xs_freeze, __FILE__);
    newXS("object::is_frozen", xs_is_frozen, __FILE__);
    newXS("object::is_locked", xs_is_locked, __FILE__);

    Perl_xs_boot_epilog(aTHX_ ax);
}
