<?php

declare(strict_types=1);

namespace Fennec\Security;

/**
 * Décide, pour un chemin donné, si l'accès est public ou protégé, et avec quel rôle minimum.
 *
 * Ne contient aucune règle en dur : les patterns concrets viennent d'un fichier de
 * configuration du projet (ex. config/firewall.php), pas du framework lui-même.
 */
final class Firewall
{
    /**
     * @param list<FirewallRule> $rules
     */
    public function __construct(private readonly array $rules)
    {
    }

    /**
     * @throws \ValueError Si une règle référence une valeur de rôle qui ne correspond à aucun cas de Role.
     */
    public static function fromConfigFile(string $filePath): self
    {
        /** @var list<array{pattern: string, public: bool, role: string|null}> $data */
        $data = require $filePath;

        $rules = \array_map(
            static fn (array $rule): FirewallRule => new FirewallRule(
                $rule['pattern'],
                $rule['public'],
                isset($rule['role']) ? Role::from($rule['role']) : null,
            ),
            $data,
        );

        return new self($rules);
    }

    /**
     * Trouve la première règle dont le pattern correspond au chemin.
     *
     * Si aucune règle ne correspond, la route est traitée comme protégée avec
     * ROLE_USER minimum (fail-closed par défaut, plutôt que public par accident).
     */
    public function resolveFor(string $path): FirewallRule
    {
        foreach ($this->rules as $rule) {
            if ($this->matches($rule->pathPattern, $path)) {
                return $rule;
            }
        }

        return new FirewallRule($path, false, Role::USER);
    }

    private function matches(string $pattern, string $path): bool
    {
        if (\str_ends_with($pattern, '*')) {
            return \str_starts_with($path, \rtrim($pattern, '*'));
        }

        return $pattern === $path;
    }
}
