package SPVM::Builder::Config::Global;

use parent 'SPVM::Builder::Config::Linker';

use strict;
use warnings;
use Carp 'confess';

use SPVM::Builder::Util;
use SPVM::Builder::Util::API;
use SPVM::Builder::Accessor 'has';

# Fields
my $fields = [qw(
  before_compile_cbs
)];

has($fields);

sub optimize {
  my $self = shift;
  
  if (@_) {
    my ($optimize, $condition) = @_;
    $self->compile_match($condition, {optimize => $optimize});
    return $self;
  }
  else {
    confess "The optimize method only supports the setter. Use match methods to configure dynamic settings.";
  }
}

sub option_names {
  my ($self) = @_;
  return [@{$self->SUPER::option_names}, @$fields];
}

# Class Methods
sub new {
  my $class = shift;
  
  my $self = $class->SUPER::new(
    before_compile_cbs => [],
    @_
  );
  
  return $self;
}

# Instance Methods
sub add_before_compile_cb {
  my ($self, @before_compile_cbs) = @_;
  
  push @{$self->{before_compile_cbs}}, @before_compile_cbs;
}

sub compile_match {
  my ($self, $condition, $match_config_or_cb) = @_;
  
  # Normalize condition for key validation
  if ($condition) {
    SPVM::Builder::Config->new_empty(%$condition);
  }
  
  # Normalize match_config if it's a hash (not a callback)
  if (ref $match_config_or_cb eq 'HASH') {
    SPVM::Builder::Config->new_empty(%$match_config_or_cb);
  }
  
  $self->add_before_compile_cb(sub {
    my ($config) = @_;
    
    &_match_apply($config, $condition, $match_config_or_cb);
  });
}

sub _match_apply {
  my ($config, $condition, $match_config_or_cb) = @_;
  
  my $match = 1;
  if ($condition) {
    for my $name (keys %$condition) {
      my $condition_value = $condition->{$name};
      my $config_value    = $config->{$name};
      
      if (ref $condition_value eq 'Regexp') {
        unless (defined $config_value && $config_value =~ $condition_value) {
          $match = 0;
          last;
        }
      }
      else {
        if (defined $condition_value) {
          unless (defined $config_value && $config_value eq $condition_value) {
            $match = 0;
            last;
          }
        }
        else {
          if (defined $config_value) {
            $match = 0;
            last;
          }
        }
      }
    }
  }
  
  # Apply configuration or execute callback
  if ($match) {
    if (ref $match_config_or_cb eq 'CODE') {
      # Execute custom logic if a callback is provided
      $match_config_or_cb->($config);
    }
    else {
      # Apply static configuration (supporting +foo syntax)
      for my $match_name (keys %$match_config_or_cb) {
        my $new_value = $match_config_or_cb->{$match_name};
        
        if ($match_name =~ /^\+(.+)$/) {
          my $name = $1;
          my $old_value = $config->{$name};
          
          if (!defined $old_value) {
            $config->{$name} = $new_value;
          }
          elsif (ref $old_value eq '' && ref $new_value eq '') {
            $config->{$name} .= $new_value;
          }
          elsif (ref $old_value eq '' && ref $new_value eq 'ARRAY') {
            $config->{$name} = [$old_value, @$new_value];
          }
          elsif (ref $old_value eq 'ARRAY' && ref $new_value eq '') {
            push @{$config->{$name}}, $new_value;
          }
          elsif (ref $old_value eq 'ARRAY' && ref $new_value eq 'ARRAY') {
            push @{$config->{$name}}, @$new_value;
          }
          else {
            my $old_value_type = ref $old_value || 'scalar(string)';
            my $new_value_type = ref $new_value || 'scalar(string)';
            confess "The addition of the \"$match_name\" field is not supported for the combination of $old_value_type and $new_value_type.";
          }
        }
        else {
          $config->{$match_name} = $new_value;
        }
      }
    }
  }
}

sub match_any { shift->compile_match(undef, @_) }

1;

=head1 Name

SPVM::Builder::Config::Global - Excutable File Config

=head1 Description

The SPVM::Builder::Config::Global class has methods to manipulate the config for the excutable file generated by L<spvmcc> command.

=head1 Usage

  use SPVM::Builder::Config::Global;
  
  my $config_global = SPVM::Builder::Config::Global->new;

=head1 Details

=head2 Warning: Should Not Change Compiler Flags

The fields for compiler flags in L<SPVM::Builder::Config> such as L<SPVM::Builder::Config/"cc">, L<SPVM::Builder::Config/"std"> should not be changed.

This is because the compiler flags are used to compile SPVM core source files and a bootstrap source file generagted by C<spvmcc> command.

=head1 Super Class

=over 2

=item * L<SPVM::Builder::Config>

=back

=head1 Fields

=head2 before_compile_cbs

  my $before_compile_cbs = $config_global->before_compile_cbs;
  $config_global->before_compile_cbs($before_compile_cbs);

Gets and sets the C<before_compile_cbs> field, an array reference of callbacks that work globally called just before the compile command L</"cc"> is executed.

This affects all compilations.

=head2 optimize

  $config->optimize($optimize, $condition);

Sets C<optimize> field for the configs that match the condition C<$condition>.

This method is a setter-only method. It calls L</"match"> internally.

If C<$condition> is not defined, the optimization setting is applied to all configs.

Examples:

  # Set -O3 for all configs
  $config->optimize('-O3');
  
  # Set -O2 for native category configs
  $config->optimize('-O2', {category => 'native'});

=head1 Methods

=head2 new

  my $config_global = SPVM::Builder::Config::Global->new(%fields);

Create a new L<SPVM::Builder::Config::Global> object with L</"Fields"> and fields of its super classes.

This method calls the C<new> method of its L<super class|/"Inheritance"> given %fields with field default values applied.

Field Default Values:

=over 2

=item * L<output_type|SPVM::Builder::Config/"output_type">

  "exe"

=item * L</"before_compile_cbs">

  []

=item * Other Fields

  undef

=back

=head2 add_before_compile_cb

  $config_global->add_before_compile_cb(@before_compile_cbs);

Adds @before_compile_cbs to the end of L</"before_compile_cbs"> field.

Examples:

  $config_global->add_before_compile_cb(sub {
    my ($config, $compile_info) = @_;
    
    my $cc_command = $compile_info->to_command;
    
    # Do something
  });

=head2 compile_match

  $global_config->compile_match($condition, $match_config_or_cb);

Adds a rule to dynamically update the configuration before compilation if the given conditions are met.

Parameters:

=over 2

=item * C<$condition>

A hash reference or an L<SPVM::Builder::Config> object specifying the match criteria. 
Each key represents a field name in the target configuration.

=over 4

=item * If the value is a C<Regexp> object (e.g., C<qr/.../>), it performs a regex match.

=item * If the value is C<undef>, it matches if the target field is also C<undef>.

=item * Otherwise, it performs a string equality check (C<eq>).

=back

=item * C<$match_config_or_cb>

A hash reference, an L<SPVM::Builder::Config> object, or a code reference (callback).

If it is a hash reference or a config object, the fields in the target configuration are updated. 
You can use the B<C<+>> prefix in the field name to B<append> values instead of overwriting them:

=over 4

=item * B<C<+field => $string>> : Concatenates the string to the existing value.

=item * B<C<+field => $array_ref>> : Pushes the elements of the array reference into the existing array. 
If the existing value is a scalar, it is promoted to an array before the push.

=back

If it is a code reference, the callback is executed with the target L<SPVM::Builder::Config> object as its first argument. 
This allows for complex, procedural updates to the configuration.

=back

=head2 compile_match_any

  $global_config->compile_match_any($match_config_or_cb);

A syntax sugar for L</"match"> with no conditions. 
The C<$match_config> will be applied to all configurations before compilation.

=cut

=head1 Copyright & License

Copyright (c) 2023 Yuki Kimoto

MIT License
