<?php

namespace App\Core;

class Router
{

  /** stores all the routes 
   * ["GET" => ["path" => "/", "action" => [...]]]
   */
  private array $routes = [];

  /**
   * route GET request
   * @param string $path
   * @param callable|array $action
   */
  public function get(string $path, callable|array $action, array $middlewares = []): void
  {
    $this->register("GET", $path, $action, $middlewares);
  }

  /**
   * route POST request
   * @param string $path
   * @param callable|array $action
   */
  public function post(string $path, callable|array $action, array $middlewares = []): void
  {
    $this->register("POST", $path, $action, $middlewares);
  }

  /** registering routes 
   * @param string $method
   * @param string $path
   * @param callable|array $action
   */
  private function register(string $method, string $path, callable|array $action, array $middlewares = []): void
  {
    $this->routes[$method][] = [
      "path" => $path,
      "action" => $action,
      'middlewares' => $middlewares
    ];
  }

  /** dispatch the route */
  public function dispatch(): void
  {
    $method = $_SERVER["REQUEST_METHOD"];
    $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

    $basePath = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])), '/');

    if ($basePath !== '' && str_starts_with($uri, $basePath)) {
      $uri = substr($uri, strlen($basePath));
    }

    $uri = $uri ?: '/';

    $routes = $this->routes[$method] ?? [];

    foreach ($routes as $route) {
      $pattern = preg_replace('/\{([^\/]+)\}/', '([^/]+)', $route["path"]);
      $pattern = "#^{$pattern}$#";

      if (preg_match($pattern, $uri, $matches)) {

        array_shift($matches);

        foreach ($route['middlewares'] as $middleware) {
          if (is_string($middleware)) {
            $middleware = new $middleware();
          }

          $middleware->handle();
        }


        $this->execute($route["action"], $matches);

        return;
      }
    }

    http_response_code(404);
    echo "404";
  }

  /** execute the action */
  private function execute(callable|array $action, array $params)
  {

    if (is_callable($action)) {
      call_user_func_array($action, $params);

      return;
    }

    [$controller, $method] = $action;

    $instance = new $controller();

    call_user_func_array([$instance, $method], $params);
  }
}
