use v5.40;
use lib '../lib', 'lib';
use blib;
use Test2::Tools::Affix qw[:all];
use Test2::V0 -no_srand => 1;
use Affix qw[:all];
use Affix::Build;
use Config;
use ExtUtils::Embed;
#
diag '$Config{useshrplib} claims to be ' . $Config{useshrplib};
diag '$Config{libperl} is ' . $Config{libperl};

#~ $Config{useshrplib} eq 'true' || exit skip_all 'Cannot embed perl in a shared lib without building a shared libperl.';
eval {
    # See https://metacpan.org/release/RJBS/perl-5.36.0/view/INSTALL#Building-a-shared-Perl-library
    #
    # Compile C Library 1 (Basic Operations)
    my $cflags  = ccopts();    #$Config{ccflags} . ' -I' . $Config{archlib} . '\CORE';
    my $ldflags = '';
    if ( $^O eq 'MSWin32' ) {
        $ldflags .= ' "' . $Config{archlib} . '/CORE/' . $Config{libperl} . '"';
    }
    elsif ( $^O eq 'darwin' ) {    # macOS/ARM64 requires ignoring undefined symbols from the host Perl
        $ldflags .= ' -Wl,-undefined,dynamic_lookup';
    }
    else {
        if ( $Config{useshrplib} && $Config{useshrplib} ne 'false' ) {
            $ldflags .= '-L"' . $Config{archlib} . '/CORE" -l' . ( $Config{libperl} =~ s/^(?:lib)?([^.]+).*$/-l$1/r );
        }
    }
    diag $cflags;
    diag $ldflags;
    my $lib = compile_ok( <<~'END', { cflags => $cflags, ldflags => $ldflags } );
        #include "std.h"
        //ext: .c
        #undef warn
        #include <EXTERN.h>
        #include <perl.h>

        #define NO_XSLOCKS
        #include <XSUB.h>

        // Takes an SV*, increments it if it's an integer
        DLLEXPORT void inc_sv(SV* sv) { dTHX;
            if (SvIOK(sv)) {
                int val = SvIV(sv);
                sv_setiv(sv, val + 1);
            }
        }

        // Returns a new SV* (Mortal)
        DLLEXPORT SV* make_sv(int val) { dTHX;
            return sv_2mortal(newSViv(val));
        }
        END

    # Test Argument Passing (SV)
    # The signature "SV" maps to "SV*" in C because Affix detects the "SV" type name
    isa_ok my $inc = wrap( $lib, 'inc_sv', [ Pointer [SV] ] => Void ), ['Affix'];
    my $val = 10;
    $inc->($val);
    is $val, 11, 'Passed SV to C, modified in place';

    # Test Return Value (SV)
    isa_ok my $make = wrap( $lib, 'make_sv', [Int] => Pointer [SV] ), ['Affix'];
    my $res = $make->(42);
    is $res, 42, 'Received SV from C';

    # Test within Callbacks
    # Define a callback type that accepts and returns an SV*
    typedef CallbackSV => Callback [ [ Pointer [SV] ] => Pointer [SV] ];

    # We need a C function that takes this callback
    my $lib2 = compile_ok( <<~'END', { cflags => $cflags, ldflags => $ldflags } );
        #include "std.h"
        //ext: .c
        #undef warn
        #include <EXTERN.h>
        #include <perl.h>

        #define NO_XSLOCKS
        #include <XSUB.h>

        // Define a C function pointer type that matches the signature:
        // Pointer[SV] -> Pointer[SV]  ==  SV* (*)(SV*)
        typedef SV* (*cb_t)(SV*);

        DLLEXPORT int call_perl(cb_t cb, int val) { dTHX;
            // Create a mortal SV to pass to the callback
            SV* arg = sv_2mortal(newSViv(val));

            // INVOKE THE CALLBACK DIRECTLY as a C function.
            // Do NOT use call_sv(); Affix handles the Perl context switching inside the trampoline 'cb'.
            SV* ret = cb(arg);

            // Extract the integer value from the returned SV
            return SvIV(ret);
        }
        END

    # Wrap the C function. Affix will automatically generate a trampoline for the coderef passed as 'cb'.
    isa_ok my $caller = wrap( $lib2, 'call_perl', [ CallbackSV(), Int ] => Int ), ['Affix'];
    my $cb = sub ($sv) {

        # Verify we received a scalar
        return $sv * 2;
    };
    is $caller->( $cb, 5 ), 10, 'Roundtrip SV through Callback';
};
skip_all 'Failed to embed perl: ' . $@ if $@;
done_testing;
