package RxPerl::Extras;

use strict;
use warnings;

use RxPerl::Operators::Creation 'rx_observable';
use RxPerl::Operators::Pipeable 'op_map';

use Exporter 'import';
our @EXPORT_OK = qw/ op_exhaust_all_with_latest op_exhaust_map_with_latest /;
our %EXPORT_TAGS = (all => \@EXPORT_OK);

our $VERSION = "v0.0.1";

sub op_exhaust_all_with_latest {
    return sub {
        my ($source) = @_;

        return rx_observable->new(sub {
            my ($subscriber) = @_;

            my $active_subscription;
            my $big_completed;
            my $own_subscription = RxPerl::Subscription->new;

            my ($owed_val, $val_is_owed);

            $subscriber->subscription->add(
                \$active_subscription,
                $own_subscription,
            );

            my $helper_sub; $helper_sub = sub {
                my ($new_obs) = @_;

                !$active_subscription or do {
                    $owed_val = $new_obs;
                    $val_is_owed = 1;
                    return;
                };

                $active_subscription = RxPerl::Subscription->new;
                my $small_subscriber = {
                    new_subscription => $active_subscription,
                    next             => sub {
                        $subscriber->{next}->(@_) if defined $subscriber->{next};
                    },
                    error            => sub {
                        $subscriber->{error}->(@_) if defined $subscriber->{error};
                    },
                    complete         => sub {
                        undef $active_subscription;
                        if ($val_is_owed) {
                            $val_is_owed = 0;
                            $helper_sub->($owed_val);
                        } else {
                            $subscriber->{complete}->(), undef $helper_sub
                                if $big_completed and defined $subscriber->{complete};
                        }
                    },
                };
                $new_obs->subscribe($small_subscriber);
            };

            my $own_subscriber = {
                new_subscription => $own_subscription,
                next             => $helper_sub,
                error            => sub {
                    $subscriber->{error}->(@_) if defined $subscriber->{error};
                },
                complete         => sub {
                    $big_completed = 1;
                    $subscriber->{complete}->(), undef $helper_sub
                        if !$active_subscription and defined $subscriber->{complete};
                },
            };

            $source->subscribe($own_subscriber);

            return;
        });
    }
}

sub op_exhaust_map_with_latest {
    my ($observable_factory) = @_;

    return sub {
        my ($source) = @_;

        return $source->pipe(
            op_map($observable_factory),
            op_exhaust_all_with_latest(),
        );
    };
}

1;
__END__

=encoding utf-8

=head1 NAME

RxPerl::Extras - extra operators for RxPerl

=head1 SYNOPSIS

    use RxPerl::Mojo qw/ ... /; # work also with RxPerl::IOAsync or RxPerl::AnyEvent
    use RxPerl::Extras 'op_exhaust_map_with_latest'; # or ':all'

    # (pause 5 seconds) 0, (pause 5 seconds) 2, complete
    rx_timer(0, 2)->pipe(
        op_take(3),
        op_exhaust_map_with_latest(sub ($val, @) {
            return rx_of($val)->pipe( op_delay(5) );
        }),
    )->subscribe($observer);

=head1 DESCRIPTION

RxPerl::Extras is a collection of original L<RxPerl> operators which the author thinks could be useful to many.

It currently contains only one pipeable operator.

=head1 EXPORTABLE FUNCTION

The code samples in this section assume C<$observer> has been set to:

    $observer = {
        next     => sub {say "next: ", $_[0]},
        error    => sub {say "error: ", $_[0]},
        complete => sub {say "complete"},
    };

=head2 PIPEABLE OPERATORS

=over

=item op_exhaust_map_with_latest

Works like L<RxPerl>'s L<op_exhaust_map|RxPerl#op_exhaust_map>, except if any new next events arrive before exhaustion,
the latest of those events will also be processed after exhaustion.

    # (pause 5 seconds) 0, (pause 5 seconds) 2, complete
    rx_timer(0, 2)->pipe(
        op_take(3),
        op_exhaust_map_with_latest(sub ($val, @) {
            return rx_of($val)->pipe( op_delay(5) );
        }),
    )->subscribe($observer);

=back

=head1 NOTIFICATIONS FOR NEW RELEASES

You can start receiving emails for new releases of this module, at L<https://perlmodules.net>.

=head1 COMMUNITY CODE OF CONDUCT

L<RxPerl's Community Code of Conduct|RxPerl::CodeOfConduct> applies to this module too.

=head1 LICENSE

Copyright (C) 2024 Alexander Karelas.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

Alexander Karelas E<lt>karjala@cpan.orgE<gt>

=cut
