<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Security;

use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

final readonly class RoleDumper
{
    public function __construct(
        private RoleProvider $roleProvider,
        private RoleHierarchyInterface $roleHierarchy,
        private TranslatorInterface $translator,
    ) {}

    public function dumpAsMarkdown(): string
    {
        $roles = $this->roleProvider->getRoles();
        $rolesWithoutScopes = $this->roleProvider->getRolesWithoutScopes();

        // Group roles by title
        $groups = [];
        foreach ($roles as $role) {
            $title = $this->roleProvider->getRoleTitle($role);
            $title ??= 'Other';
            $groups[$title][] = $role;
        }

        // Sort groups by title
        ksort($groups, SORT_NATURAL | SORT_FLAG_CASE);

        $lines = [];
        foreach ($groups as $title => $roleList) {
            // Sort roles by translated label for deterministic output
            usort($roleList, function (string $a, string $b): int {
                $ta = $this->translator->trans($a);
                $tb = $this->translator->trans($b);

                return strcasecmp($ta, $tb);
            });

            $translatedTitle = $this->translator->trans($title);
            $lines[] = '## '.$translatedTitle;

            foreach ($roleList as $role) {
                // Translate primary role
                $translatedRole = $this->translator->trans($role);

                // Scope marker: (S) if needs scope, (~~S~~) if no scope required
                $needsScope = !in_array($role, $rolesWithoutScopes, true);
                $scopeMarker = $needsScope ? '(S)' : '(~~S~~)';

                // Compute dependent roles from hierarchy (exclude itself)
                $reachable = $this->roleHierarchy->getReachableRoleNames([$role]);
                $dependents = array_values(array_filter($reachable, static fn (string $r): bool => $r !== $role));

                // Translate dependents and sort deterministically
                $translatedDependents = array_map(fn (string $r) => $this->translator->trans($r), $dependents);
                sort($translatedDependents, SORT_NATURAL | SORT_FLAG_CASE);

                if (count($translatedDependents) > 0) {
                    $lines[] = sprintf('- **%s** %s: %s', $translatedRole, $scopeMarker, implode(', ', $translatedDependents));
                } else {
                    $lines[] = sprintf('- **%s** %s', $translatedRole, $scopeMarker);
                }
            }

            // Add a blank line between groups
            $lines[] = '';
        }

        // Trim possible trailing blank line
        $markdown = rtrim(implode("\n", $lines));

        return $markdown."\n"; // End with newline for POSIX friendliness
    }
}
