package MooseX::Extended;

# ABSTRACT: Extend Moose with safe defaults and useful features

use 5.20.0;
use warnings;

use Moose::Exporter;
use MooseX::Extended::Types ':all';
use Moose                     ();
use MooseX::StrictConstructor ();
use mro                       ();
use namespace::autoclean      ();
use Module::Load 'load';
use MooseX::Extended::Core qw(
  _assert_import_list_is_valid
  _debug
  _disabled_warnings
  _enabled_features
  _our_import
  _our_init_meta
  field
  param
);
use feature _enabled_features();
no warnings _disabled_warnings();
use B::Hooks::AtRuntime 'after_runtime';
use Import::Into;

our $VERSION = '0.23';

sub import {
    my ( $class, %args ) = @_;
    $args{_import_type} = 'class';
    my $target_class = _assert_import_list_is_valid( $class, \%args );
    my @with_meta    = grep { not $args{excludes}{$_} } qw(field param);
    if (@with_meta) {
        @with_meta = ( with_meta => [@with_meta] );
    }
    my ( $import, undef, undef ) = Moose::Exporter->setup_import_methods(
        @with_meta,
        install => [qw/unimport/],
        also    => ['Moose'],
    );
    _our_import( $class, $import, $target_class );
}

# Internal method setting up exports. No public
# documentation by design
sub init_meta ( $class, %params ) {
    Moose->init_meta(%params);
    _our_init_meta( $class, \&_apply_default_features, %params );
}

# XXX we don't actually use the $params here, even though we need it for
# MooseX::Extended::Role. But we need to declare it in the signature to make
# this code work
sub _apply_default_features ( $config, $for_class, $params = undef ) {
    if ( my $types = $config->{types} ) {
        _debug("$for_class: importing types '@$types'");
        MooseX::Extended::Types->import::into( $for_class, @$types );
    }

    MooseX::StrictConstructor->import( { into => $for_class } ) unless $config->{excludes}{StrictConstructor};
    Carp->import::into($for_class)                              unless $config->{excludes}{carp};
    namespace::autoclean->import::into($for_class)              unless $config->{excludes}{autoclean};

    # see perldoc -v '$^P'
    if ($^P) {
        say STDERR "We are running under the debugger or using code that uses debugger code (e.g., Devel::Cover). $for_class is not immutable";
    }
    else {
        unless ( $config->{excludes}{immutable} ) {

            # after_runtime is loaded too late under the debugger
            eval {
                load B::Hooks::AtRuntime, 'after_runtime';
                after_runtime {
                    $for_class->meta->make_immutable;
                    if ( $config->{debug} ) {

                        # they're doing debug on a class-by-class basis, so
                        # turn this off after the class compiles
                        $MooseX::Extended::Debug = 0;
                    }
                };
                1;
            } or do {
                my $error = $@;
                warn
                  "Could not load 'B::Hooks::AtRuntime': $error. You class is not immutable. You can `use MooseX::Extended excludes => ['immutable'];` to suppress this warning.";
            };
        }
    }
    unless ( $config->{excludes}{true} ) {
        eval {
            load true;
            true->import::into($for_class);    # no need for `1` at the end of the module
            1;
        } or do {
            my $error = $@;
            warn
              "Could not load 'true': $error. Your class must end in a true value. You can `use MooseX::Extended excludes => ['true'];` to suppress this warning.";
        };
    }

    # If we never use multiple inheritance, this should not be needed.
    mro::set_mro( $for_class, 'c3' )
      unless $config->{excludes}{c3};

    feature->import( _enabled_features() );
    warnings->unimport(_disabled_warnings);
}
1;

__END__

=pod

=encoding UTF-8

=head1 NAME

MooseX::Extended - Extend Moose with safe defaults and useful features

=head1 VERSION

version 0.23

=head1 SYNOPSIS

    package My::Names {
        use MooseX::Extended types => [qw(compile Num NonEmptyStr Str PositiveInt ArrayRef)];
        use List::Util 'sum';

        # the distinction between `param` and `field` makes it easier to
        # see which are available to `new`
        param _name => ( isa => NonEmptyStr, init_arg => 'name' );
        param title => ( isa => Str,         required => 0 );

        # forbidden in the constructor
        field created => ( isa => PositiveInt, default => sub {time} );

        sub name ($self) {
            my $title = $self->title;
            my $name  = $self->_name;
            return $title ? "$title $name" : $name;
        }

        sub add ( $self, $args ) {
            state $check = compile( ArrayRef [ Num, 1 ] );    # at least one number
            ($args) = $check->($args);
            return sum( $args->@* );
        }

        sub warnit ($self) {
            carp("this is a warning");
        }
    }

=head1 DESCRIPTION

This module is B<BETA> code. It's feature-complete for release and has no
known bugs. We believe it's ready for production, but make no promises.

This is a quick overview. See L<MooseX::Extended::Manual::Tutorial> for more
information.

This class attempts to create a safer version of Moose that defaults to
read-only attributes and is easier to read and write.

It tries to bring some of the lessons learned from L<the Corinna
project|https://github.com/Ovid/Cor>, while acknowledging that you can't
always get what you want (such as true encapsulation and true methods).

This:

    package My::Class {
        use MooseX::Extended;

        ... your code here
    }

Is sort of the equivalent to:

    package My::Class {
        use v5.20.0;
        use Moose;
        use MooseX::StrictConstructor;
        use feature qw( signatures postderef postderef_qq);
        no warnings qw( experimental::signatures experimental::postderef );
        use namespace::autoclean;
        use Carp;
        use mro 'c3';

        ... your code here

        __PACKAGE__->meta->make_immutable;
    }
    1;

It also exports two functions which are similar to Moose C<has>: C<param> and
C<field>.

A C<param> is a required parameter (defaults may be used). A C<field> is not
intended to be passed to the constructor.

Note that the C<has> function is still available, even if it's not needed.

Also, while your author likes the postfix block syntax, it's not required. You
can even safely inline multiple packages in the same file:

    package My::Point;
    use MooseX::Extended types => [qw/Num/];

    param [ 'x', 'y' ] => ( isa => Num );

    package My::Point::Mutable;
    use MooseX::Extended;
    extends 'My::Point';

    param [ '+x', '+y' ] => ( writer => 1, clearer => 1, default => 0 );

    sub invert ($self) {
        my ( $x, $y ) = ( $self->x, $self->y );
        $self->set_x($y);
        $self->set_y($x);
    }

    # MooseX::Extended will cause this to return true, even if we try to return
    # false
    0;

=head1 CONFIGURATION

You may pass an import list to L<MooseX::Extended>.

    use MooseX::Extended
      excludes => [qw/StrictConstructor carp/],      # I don't want these features
      types    => [qw/compile PositiveInt HashRef/]; # I want these type tools

=head2 C<types>

Allows you to import any types provided by L<MooseX::Extended::Types>.

This:

    use MooseX::Extended::Role types => [qw/compile PositiveInt HashRef/];

Is identical to this:

    use MooseX::Extended::Role;
    use MooseX::Extended::Types qw( compile PositiveInt HashRef );

=head2 C<excludes>

You may find some features to be annoying, or even cause potential bugs (e.g.,
if you have a C<croak> method, our importing of C<Carp::croak> will be a
problem. You can exclude the following:

=over 4

=item * C<StrictConstructor>

    use MooseX::Extended excludes => ['StrictConstructor'];

Excluding this will no longer import C<MooseX::StrictConstructor>.

=item * C<autoclean>

    use MooseX::Extended excludes => ['autoclean'];

Excluding this will no longer import C<namespace::autoclean>.

=item * C<c3>

    use MooseX::Extended excludes => ['c3'];

Excluding this will no longer apply the C3 mro.

=item * C<carp>

    use MooseX::Extended excludes => ['carp'];

Excluding this will no longer import C<Carp::croak> and C<Carp::carp>.

=item * C<immutable>

    use MooseX::Extended excludes => ['immutable'];

Excluding this will no longer make your class immutable.

=item * C<true>

    use MooseX::Extended excludes => ['true'];

Excluding this will require your module to end in a true value.

=item * C<param>

    use MooseX::Extended excludes => ['param'];

Excluding this will make the C<param> function unavailable.

=item * C<field>

    use MooseX::Extended excludes => ['field'];

Excluding this will make the C<field> function unavailable.

=back

=head2 C<includes>

Some experimental features are useful, but might not be quite what you want.

By default, L<MooseX::Extended> tries to be relatively conservative. However,
you might want to turn it up to 11. There are optional, B<EXPERIMENTAL>
features you can use for this. They're turned by the C<includes> flag.

=over 4

=item * C<multi>

    use MooseX::Extended includes => [qw/multi/];

    multi sub foo ($self, $x)      { ... }
    multi sub foo ($self, $x, $y ) { ... }

Allows you to redeclare a method (or subroutine) and the dispatch will use the number
of arguments to determine which subroutine to use. Note that "slurpy" arguments such as
arrays or hashes will take precedence over scalars:

    multi sub foo ($self, @x) { ... }
    multi sub foo ($self, $x) { ... } # will never be called

It's quite possible to define multi subs that are ambiguous:

    package Foo {
        use MooseX::Extended includes => [qw/multi/];

        multi sub foo ($self, @bar) { return '@bar' }
        multi sub foo ($self, $bar) { return '$bar' }
    }

    say +Foo->new->foo(1);
    say +Foo->new->foo(1,2,3);

Both of the above will print the string C<@bar>. The second definition of
C<foo> is effectively lost.

Only available on Perl v5.26.0 or higher. Requires L<Syntax::Keyword::MultiSub>.

=item * C<async>

    package My::Thing {
        use MooseX::Extended
        types    => [qw/Str/],
        includes => ['async'];
        use IO::Async::Loop;

        field output => ( is => 'rw', isa => Str, default => '' );

        async sub doit ( $self, @list ) {
            my $loop = IO::Async::Loop->new;
            $self->output('> ');
            foreach my $item (@list) {
                await $loop->delay_future( after => 0.01 );
                $self->output( $self->output . "$item " );
            }
        }
    }

Allows you to write asynchronous code with C<async> and C<await>.

Only available on Perl v5.26.0 or higher. Requires L<Future::AsyncAwait>.

=back

=head1 REDUCING BOILERPLATE

Let's say you've settled on the following feature set:

    use MooseX::Extended
        excludes => [qw/StrictConstructor carp/],
        includes => [qw/multi/];

And you keep typing that over and over. We've removed a lot of boilerplate,
but we've added different boilerplate. Instead, just create
C<My::Custom::Moose> and C<use My::Custom::Moose;>. See
L<MooseX::Extended::Custom> for details.

=head1 IMMUTABILITY

=head2 Making Your Class Immutable

You no longer need to end your Moose classes with:

    __PACKAGE__->meta->make_immutable;

That prevents further changes to the class and provides some optimizations to
make the code run much faster. However, it's somewhat annoying to type. We do
this for you, via L<B::Hooks::AtRuntime>. You no longer need to do this yourself.

=head2 Making Your Instance Immutable

By default, attributes defined via C<param> and C<field> are read-only.
However, if they contain a reference, you can fetch the reference, mutate it,
and now everyone with a copy of that reference has mutated state.

To handle that, we offer a new C<< clone => $clone_type >> pair for attributes.

See the L<MooseX::Extended::Manual::Cloning> documentation.

=head1 OBJECT CONSTRUCTION

Objection construction for L<MooseX::Extended> is like Moose, so no
changes are needed.  However, in addition to C<has>, we also provide C<param>
and C<field> attributes, both of which are C<< is => 'ro' >> by default.

The C<param> is I<required>, whether by passing it to the constructor, or using
C<default> or C<builder>.

The C<field> is I<forbidden> in the constructor and is lazy if it has a
builder, because that builder is often dependent on attributes set in the
constructor (and why call it if it's not used?).

Here's a short example:

    package Class::Name {
        use MooseX::Extended types => [qw(compile Num NonEmptyStr Str)];

        # these default to 'ro' (but you can override that) and are required
        param _name => ( isa => NonEmptyStr, init_arg => 'name' );
        param title => ( isa => Str,         required => 0 );

        # fields must never be passed to the constructor
        # note that ->title and ->name are guaranteed to be set before
        # this because fields are lazy by default
        field name => (
            isa     => NonEmptyStr,
            default => sub ($self) {
                my $title = $self->title;
                my $name  = $self->_name;
                return $title ? "$title $name" : $name;
            },
        );
    }

See L<MooseX::Extended::Manual::Construction> for a full explanation.

=head1 ATTRIBUTE SHORTCUTS

When using C<field> or C<param>, we have some attribute shortcuts:

    param name => (
        isa       => NonEmptyStr,
        writer    => 1,   # set_name
        reader    => 1,   # get_name
        predicate => 1,   # has_name
        clearer   => 1,   # clear_name
        builder   => 1,   # _build_name
    );

    sub _build_name ($self) {
        ...
    }

See L<MooseX::Extended::Manual::Shortcuts> for a full explanation.

=head1 INVALID ATTRIBUTE DEFINITIONS

The following L<Moose> code will print C<WhoAmI>. However, the second attribute
name is clearly invalid.

    package Some::Class {
        use Moose;

        has name   => ( is => 'ro' );
        has '-bad' => ( is => 'ro' );
    }

    my $object = Some::Class->new( name => 'WhoAmI' );
    say $object->name;

C<MooseX::Extended> will throw a L<Moose::Exception::InvalidAttributeDefinition> exception
if it encounters an illegal method name for an attribute.

This also applies to various attributes which allow method names, such as
C<clone>, C<builder>, C<clearer>, C<writer>, C<reader>, and C<predicate>.

Trying to pass a defined C<init_arg> to C<field> will also throw this
exception, unless the init_arg begins with an underscore. (It is sometimes
useful to be able to define an C<init_arg> for unit testing.)

=head1 DEBUGGER SUPPORT

When running L<MooseX::Extended> under the debugger, there are some
behavioral differences you should be aware of.

=over 4

=item * Your classes won't be immutable

Ordinarily, we call C<< __PACKAGE__->meta->make_immutable >> for you. This
relies on L<B::Hooks::AtRuntime>'s C<after_runtime> function. However, that
runs too late under the debugger and dies. Thus, we disable this feature under
the debugger. Your classes may run a bit slower, but hey, it's the debugger!

=item * C<namespace::autoclean> will frustrate you

It's very frustrating when running under the debugger and doing this:

	13==>       my $total = sum(3,4,5);
	DB<4>
	Undefined subroutine &main::sum called at (eval 423) ...

We had removed C<namespace::autoclean> when running under the debugger, but
backed that out: L<https://github.com/Ovid/moosex-extreme/issues/11>.

=back

=head1 MANUAL

=over 4

=item * L<MooseX::Extended::Manual::Overview>

=item * L<MooseX::Extended::Manual::Construction>

=item * L<MooseX::Extended::Manual::Shortcuts>

=item * L<MooseX::Extended::Manual::Cloning>

=back

=head1 RELATED MODULES

=over 4

=item * L<MooseX::Extended::Types> is included in the distribution.

This provides core types for you.

=item * L<MooseX::Extended::Role> is included in the distribution.

C<MooseX::Extended>, but for roles.

=back

=head1 TODO

Some of this may just be wishful thinking. Some of this would be interesting if
others would like to collaborate.

=head2 Configurable Types

We provide C<MooseX::Extended::Types> for convenience, along with the C<declare> 
function. We should write up (and test) examples of extending it.

=head2 C<BEGIN::Lift>

This idea maybe belongs in C<MooseX::Extended::OverKill>, but ...

Quite often you see things like this:

    BEGIN { extends 'Some::Parent' }

Or this:

    sub serial_number; # required by a role, must be compile-time
    has serial_number => ( ... );

In fact, there are a variety of Moose functions which would work better if
they ran at compile-time instead of runtime, making them look a touch more
like native functions. My various attempts at solving this have failed, but I
confess I didn't try too hard.

=head1 NOTES

There are a few things you might be interested to know about this module when
evaluating it.

Most of this is written with bog-standard L<Moose>, so there's nothing
terribly weird inside, but you may wish to note that we use
L<B::Hooks::AtRuntime> and L<true>. They seem sane, but I<caveat emptor>.

This module was originally released on github as C<MooseX::Extreme>, but
enough people pointed out that it was not extreme at all. That's why the
repository is L<https://github.com/Ovid/moosex-extreme/>.

=head1 SEE ALSO

=over 4

=item * L<Corinna|https://github.com/Ovid/Cor>

The RFC of the new version of OOP planned for the Perl core.

=item * L<MooseX::Modern|https://metacpan.org/pod/MooseX::Modern>

MooseX::Modern - Precision classes for Modern Perl

=item * L<Zydeco|https://metacpan.org/pod/Zydeco>

Zydeco - Jazz up your Perl

=item * L<Dios|https://metacpan.org/pod/Dios>

Dios - Declarative Inside-Out Syntax

=back

=head1 AUTHOR

Curtis "Ovid" Poe <curtis.poe@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2022 by Curtis "Ovid" Poe.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

=cut
