<?php

namespace App\Lib;

use Closure;

class Router
{
    private $routes = [];
    private $middleware = [];
    private $currentGroupPattern = '';
    private $currentGroupMiddleware = [];

    public static function create(): self
    {
        return new self();
    }

    public function get(string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute('GET', $pattern, $handler);
    }

    public function post(string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute('POST', $pattern, $handler);
    }

    public function put(string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute('PUT', $pattern, $handler);
    }

    public function delete(string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute('DELETE', $pattern, $handler);
    }

    public function patch(string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute('PATCH', $pattern, $handler);
    }

    public function any(string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute(['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], $pattern, $handler);
    }

    public function map(array $methods, string $pattern, $handler): RouteCollectorProxy
    {
        return $this->addRoute($methods, $pattern, $handler);
    }

    public function redirect(string $from, string $to, int $status = 302): RouteCollectorProxy
    {
        return $this->get($from, function() use ($to, $status) {
            header("Location: $to", true, $status);
            exit;
        });
    }

    public function group(string $pattern, callable $callback): RouteCollectorProxy
    {
        $previousGroupPattern = $this->currentGroupPattern;
        $previousGroupMiddleware = $this->currentGroupMiddleware;

        $this->currentGroupPattern .= $pattern;

        $proxy = new RouteCollectorProxy($this);
        $callback($proxy);

        $this->currentGroupPattern = $previousGroupPattern;
        $this->currentGroupMiddleware = $previousGroupMiddleware;

        return $proxy;
    }

    public function add($middleware): RouteCollectorProxy
    {
        $this->currentGroupMiddleware[] = $middleware;
        return new RouteCollectorProxy($this);
    }

    private function addRoute($methods, string $pattern, $handler): RouteCollectorProxy
    {
        $pattern = $this->currentGroupPattern . $pattern;
        $middleware = $this->currentGroupMiddleware;

        foreach ((array)$methods as $method) {
            $this->routes[$method][] = [
                'pattern' => $pattern,
                'handler' => $handler,
                'middleware' => $middleware
            ];
        }

        return new RouteCollectorProxy($this);
    }

    public function run(): void
    {
        $method = $_SERVER['REQUEST_METHOD'];
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

        foreach ($this->routes[$method] ?? [] as $route) {
            $pattern = $this->buildPattern($route['pattern']);
            if (preg_match($pattern, $uri, $matches)) {
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                $this->runMiddleware(array_merge($this->middleware, $route['middleware']));
                $this->handleRoute($route['handler'], $params);
                return;
            }
        }

        $this->handleNotFound();
    }

    private function buildPattern(string $pattern): string
    {
        // Handle optional segments
        $pattern = preg_replace('/\[({(.*?)})\]/', '(?:/$2)?', $pattern);

        // Handle custom regex patterns
        $pattern = preg_replace_callback('/{(.*?)(:(.*?))?}/', function ($matches) {
            $name = $matches[1];
            $regex = $matches[3] ?? '[^/]+';
            return '(?P<' . $name . '>' . $regex . ')';
        }, $pattern);

        return '#^' . $pattern . '$#';
    }

    private function runMiddleware(array $middleware): void
    {
        foreach ($middleware as $m) {
            if (is_callable($m)) {
                $m();
            } elseif (is_object($m) && method_exists($m, 'handle')) {
                $m->handle();
            }
        }
    }

    private function handleRoute($handler, array $params): void
    {
        if (is_callable($handler)) {
            call_user_func_array($handler, $params);
        } elseif (is_array($handler) && count($handler) === 2) {
            $class = $handler[0];
            $method = $handler[1];
            if (class_exists($class) && method_exists($class, $method)) {
                call_user_func_array([new $class, $method], $params);
            }
        }
    }

    private function handleNotFound(): void
    {
        http_response_code(404);
        echo "404 Not Found";
    }
}