use Test;
use lib 'lib';
use Noise::Simplex::Native;

# RAII contract:
#   - dispose() is idempotent
#   - using a closure after dispose() throws cleanly (not a segfault)
#   - DESTROY can be called explicitly without crashing
#   - implicit GC drop runs DESTROY without crashing

plan 5;

# 1. dispose is idempotent
{
    my $s = Noise::Simplex::Native::Simplex.new(seed => 42);
    $s.dispose;
    lives-ok { $s.dispose }, 'dispose() is idempotent';
}

# 2. sample after dispose throws
{
    my $s = Noise::Simplex::Native::Simplex.new(seed => 42);
    my &n2d = $s.create-noise2d;
    $s.dispose;
    throws-like { n2d(1.0, 2.0) }, Exception,
        message => /'after dispose'/,
        '2D sample after dispose throws cleanly';
}

# 3. 3D sample after dispose throws
{
    my $s = Noise::Simplex::Native::Simplex.new(seed => 42);
    my &n3d = $s.create-noise3d;
    $s.dispose;
    throws-like { n3d(1.0, 2.0, 3.0) }, Exception,
        message => /'after dispose'/,
        '3D sample after dispose throws cleanly';
}

# 4. Explicit DESTROY is safe
{
    my $s = Noise::Simplex::Native::Simplex.new(seed => 42);
    lives-ok { $s.DESTROY }, 'explicit DESTROY runs cleanly';
}

# 5. Implicit GC drop — create+drop many instances in a tight loop and
# force garbage collection. If DESTROY were unsafe (double-free, null
# deref, etc.), MoarVM would crash here. Passing without segfault is
# the test.
{
    for ^200 {
        my $s = Noise::Simplex::Native::Simplex.new(seed => $_);
        my &n2d = $s.create-noise2d;
        n2d($_ / 7, $_ / 11);
    }
    # Best-effort: nudge the GC. Raku has no portable force-collect, but
    # the loop above is enough to create plenty of pressure.
    pass 'created and dropped 200 instances under GC pressure';
}

done-testing;
