# Copyright (c) 2025 Yuki Kimoto
# MIT License

class Mojolicious::Routes::Match {
  version_from Mojolicious;
  
  use Mojolicious::Routes::Route;
  use Mojolicious::Routes::Match::StackData;
  
  # Fields
  has root : rw Mojolicious::Routes;
  
  has endpoint : ro Mojolicious::Routes::Route;
  
  has stack : rw Mojolicious::Routes::Match::StackData[];
  
  has position : rw int;
  
  # Undocumented Fields
  has captures : Hash;
  
  # Class Methods
  static method new : Mojolicious::Routes::Match () {
    
    my $self = new Mojolicious::Routes::Match;
    
    $self->{stack} = new Mojolicious::Routes::Match::StackData[0];
    
    return $self;
  }
  
  # Instance Methods
  method find : void ($options : Hash) {
    
    $self->_match($self->root, $options);
  }
  
  private method _match : int ($r : Mojolicious::Routes::Route, $options : Hash) {
    
    my $path = copy $options->get("path")->(string);
    my $path_save = copy $path;
    
    $self->{captures} //= Hash->new;
    my $captures_save = $self->{captures}->clone;
    
    Fn->defer([$self : Mojolicious::Routes::Match, $options : Hash, $path_save : string, $captures_save : Hash] method : void () {
      $options->{"path"} = $path_save;
      $self->{captures} = $captures_save;
    });
    
    # Pattern
    my $is_endpoint = $r->is_endpoint;
    my $captures = $r->pattern->match_partial((mutable string)$path);
    unless ($captures) {
      return 0;
    }
    
    $options->set(path => $path);
    
    for my $key (@{$captures->keys}) {
      my $value = $captures->{$key};
      $self->{captures}->{$key} = $value;
    }
    
    $captures = $self->{captures};
    
    # Method
    my $method_match = 0;
    if ($r->methods) {
      my $method = $options->get("method")->(string);
      for my $_ (@{$r->methods}) {
        if ($method eq $_) {
          $method_match = 1;
          last;
        }
      }
    }
    else {
      $method_match = 1;
    }
    unless ($method_match) {
      return 0;
    }
    
    # WebSocket
    if ($r->is_websocket && !($options->{"websocket"} ? $options->{"websocket"}->(int) : 0)) {
      return 0;
    }
    
    my $empty = !length $path || $path eq "/";
    
    my $stack = (List of Mojolicious::Routes::Match::StackData)List->new_ref($self->stack);
    # Endpoint (or intermediate destination)
    if (($is_endpoint && $empty) || $r->inline) {
      my $stack_data = Mojolicious::Routes::Match::StackData->new;
      $stack_data->set_captures($captures->clone);
      $stack_data->set_route($r);
      $stack->push($stack_data);
      if ($is_endpoint && $empty) {
        $self->{endpoint} = $r;
        return 1;
      }
    }
    
    # Match children
    my $snapshot = $r->parent ? (Mojolicious::Routes::Match::StackData[])$stack->to_array : new Mojolicious::Routes::Match::StackData[0];
    for my $child (@{$r->children}) {
      if ($self->_match($child, $options)) {
        return 1;
      }
      $self->set_stack($snapshot);
    }
  }

}