package Pcore::ExtJS v0.5.0;

use Pcore -dist, -class;
use Pcore::ExtJS::Raw;
use Pcore::ExtJS::Call;
use Pcore::ExtJS::Func;
use overload    #
  q[&{}] => sub ( $self, @ ) {
    my $cb = delete $self->{cb};

    die q[ExtJS class already generated] if !$cb;

    return sub {
        $cb->( $self, @_ );

        return;
    };
  },
  fallback => undef;

has req       => ( is => 'ro', isa => InstanceOf ['Pcore::HTTP::Server::Request'],  required => 1 );
has namespace => ( is => 'ro', isa => ConsumerOf ['Pcore::App::Controller::ExtJS'], required => 1 );
has class_name => ( is => 'ro', isa => Str,     required => 1 );
has extend     => ( is => 'ro', isa => Str,     required => 1 );
has cb         => ( is => 'ro', isa => CodeRef, required => 1 );

has readable => ( is => 'ro', isa => Bool, default => 0 );    # generate readable JSON

has requires => ( is => 'ro', isa => HashRef, default => sub { {} }, init_arg => undef );
has cfg => ( is => 'ro', isa => HashRef, init_arg => undef );

has js_gen_cache => ( is => 'ro', isa => HashRef, init_arg => undef );    # cache for JS functions strings

# preload bootstrap data
our $BOOTSTRAP = P->cfg->load( $ENV->share->get('/data/extjs-v6.2.0.json') or die q["/data/extjs-v6.2.0.json" was not found] );

our $EXT_CLASS_CACHE = {};                                                # cache for ext_class resolver

# merge bootstrap data
{
    my $bootstrap_pcore = P->cfg->load( $ENV->share->get('/data/extjs-pcore.json') or die q["/data/extjs_pcore.json" was not found] );

    my $merge = sub ( $from, $to ) {
        for my $real_class_name ( keys $from->%* ) {

            # register real class name
            $to->{class}->{$real_class_name} = $real_class_name;

            # register class alias
            my $alias = $from->{$real_class_name};

            $to->{class_to_alias}->{$real_class_name} = $alias;

            # add alias -> real_class_name mapping
            $to->{alias_to_class}->{$alias} = $real_class_name;
        }

        return;
    };

    $merge->( $bootstrap_pcore->{classic}->{class_to_alias}, $BOOTSTRAP->{classic} ) if $bootstrap_pcore->{classic}->{class_to_alias};

    $merge->( $bootstrap_pcore->{modern}->{class_to_alias}, $BOOTSTRAP->{modern} ) if $bootstrap_pcore->{modern}->{class_to_alias};
}

# JS GENERATORS
sub js_raw ( $self, $js ) {
    return bless {
        ext => $self,
        js  => $js,
      },
      'Pcore::ExtJS::Raw';
}

sub js_call ( $self, $func_name, $func_args = undef ) {
    return bless {
        ext       => $self,
        func_name => $func_name,
        func_args => $func_args,
      },
      'Pcore::ExtJS::Call';
}

sub js_func ( $self, @ ) {
    my ( $func_name, $func_args, $func_body );

    if ( @_ == 2 ) {
        $func_body = $_[1];
    }
    elsif ( @_ == 3 ) {
        $func_body = $_[2];

        if ( ref $_[1] eq 'ARRAY' ) {
            $func_args = $_[1];
        }
        else {
            $func_name = $_[1];
        }
    }
    elsif ( @_ == 4 ) {
        ( $func_name, $func_args, $func_body ) = ( $_[1], $_[2], $_[3] );
    }
    else {
        die q[Invalid params];
    }

    return bless {
        ext       => $self,
        func_name => $func_name,
        func_args => $func_args,
        func_body => $func_body,
      },
      'Pcore::ExtJS::Func';
}

# EXTJS GENERATORS
sub ext_class ( $self, $name ) {
    my $framework = 'classic';

    my $bootstrap = $BOOTSTRAP->{$framework};

    my $cache_id = "$framework-$self->{namespace}->{ext_namespace}-$name";

    my $real_class_name = $EXT_CLASS_CACHE->{$cache_id};

    if ( !$real_class_name ) {

        # $name is full ExtJS alias or full ExtJS class name
        if ( index( $name, q[.] ) != -1 ) {

            # $name is ExtJS alias name or full ExtJS class name
            $real_class_name = $bootstrap->{alias_to_class}->{$name} // $bootstrap->{class}->{$name};
        }

        # $name is full or related perl class name
        else {

            # find real class name
            if ( index( $name, q[:] ) == -1 ) {
                my $class_name = ucfirst lc $name;

                # realted to the current namespace
                $real_class_name = $bootstrap->{class}->{ ref( $self->{namespace} ) . "\::$class_name" };
            }
            else {

                # can be full perl class name or realted to the current app namespace
                $real_class_name = $bootstrap->{class}->{$name} // $bootstrap->{class}->{"$self->{namespace}->{perl_ext_app_namespace}\::$name"};
            }
        }

        $EXT_CLASS_CACHE->{$cache_id} = $real_class_name if $real_class_name;
    }

    die qq[Can't resolve ExtJS name "$name"] if !$real_class_name;

    # register class in requires
    $self->{requires}->{$real_class_name} = undef;

    # return full ExtJS class name
    return $real_class_name;
}

sub ext_alias ( $self, $name ) {
    my $framework = 'classic';

    my $bootstrap = $BOOTSTRAP->{$framework};

    my $real_class_name = $self->ext_class($name);

    my $alias = $bootstrap->{class_to_alias}->{$real_class_name};

    die qq[ExtJS class name "$real_class_name" has no alias] if !$alias;

    # return full ExtJS alias name
    return $alias;
}

sub ext_type ( $self, $name ) {
    my $framework = 'classic';

    my $bootstrap = $BOOTSTRAP->{$framework};

    my $real_class_name = $self->ext_class($name);

    my $alias = $bootstrap->{class_to_alias}->{$real_class_name};

    die qq[ExtJS class name "$real_class_name" has no alias] if !$alias;

    # extract type from alias, alias_namespace.type
    my ($type) = ( $alias =~ /.+[.]([^.]+)\z/sm );

    # return ExtJS type
    return $type;
}

sub api_method ( $self, $method_id ) {
    my $map = $self->{app}->{api}->{map};

    # add version to relative method id
    $method_id = "/$self->{namespace}->{api_ver}/$method_id" if substr( $method_id, 0, 1 ) ne q[/];

    my $method = $map->get_method($method_id) // die qq[API method "$method_id" is not exists];

    return "$map->{extjs_namespace}.$method->{extjs_action}.$method->{method_name}";
}

# SERIALIZER
sub to_js ( $self ) {
    my $cfg = $self->{cfg};

    # add "extend" property
    $cfg->{extend} = $self->ext_class( $self->{extend} );

    # create "requires" property
    for my $require ( sort keys $self->{requires}->%* ) {
        push $cfg->{requires}->@*, $require;
    }

    # set alias
    if ( my $alias = eval { $self->ext_alias( $self->{class_name} ) } ) {
        $cfg->{alias} = $alias;
    }

    # TODO set type / xtype

    my $js = $self->js_call( 'Ext.define', [ "$self->{namespace}->{ext_namespace}.$self->{class_name}", $cfg ] )->to_js;

    my $js_gen_cache = $self->{js_gen_cache};

    $js->$* =~ s/"__JS(\d+)__"/$js_gen_cache->{$1}->$*/smge;

    return $js;
}

1;
__END__
=pod

=encoding utf8

=head1 NAME

Pcore::ExtJS

=head1 SYNOPSIS

=head1 DESCRIPTION

=head1 ATTRIBUTES

=head1 METHODS

=head1 SEE ALSO

=head1 AUTHOR

zdm <zdm@softvisio.net>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by zdm.

=cut
