<?php
/**
 * DocCatalog — autorité unique pour la documentation in-app.
 *
 * Construit, POUR UN UTILISATEUR DONNÉ, la liste des documents qu'il a le droit
 * de consulter, répartis en quatre familles :
 *   - general_admin : administration générale      → réservé app_admin
 *   - module_admin  : administration d'un module    → rôle <module>_admin
 *   - general_user  : utilisation générale          → tout utilisateur connecté
 *   - module_user   : utilisation d'un module        → rôle <module>_user / _admin
 *
 * Le même calcul d'autorisation sert à LISTER (forUser) et à OUVRIR (resolve) un
 * document : il est donc impossible d'accéder à un doc hors de son périmètre, et
 * les chemins ne proviennent jamais de l'entrée utilisateur (liste blanche).
 *
 * Classe sans état (méthodes statiques), cohérente avec les repositories.
 */
class DocCatalog
{
    /** Familles, dans l'ordre d'affichage. */
    public const GROUPS = ['general_admin', 'module_admin', 'general_user', 'module_user'];

    /**
     * Documentation générale (racine docs/). basename => famille.
     * 'admin' ⇒ general_admin (app_admin) ; 'user' ⇒ general_user (connecté).
     */
    private const GENERAL = [
        'guide-utilisateur.md'        => 'user',
        'guide-administrateur.md'     => 'admin',
        'architecture-technique.md'   => 'admin',
        'installation-deploiement.md' => 'admin',
        'installation-rhel.md'        => 'admin',
        'authentification-ldap.md'    => 'admin',
        'corrections-2025-04.md'      => 'admin',
    ];

    /**
     * Liste des docs autorisés pour l'utilisateur, groupés par famille.
     * @return array<string, list<array{slug:string,title:string}>>
     */
    public static function forUser(?array $user): array
    {
        $groups = array_fill_keys(self::GROUPS, []);
        foreach (self::build($user) as $entry) {
            $groups[$entry['group']][] = [
                'slug'  => $entry['slug'],
                'title' => $entry['title'],
            ];
        }
        return $groups;
    }

    /**
     * Résout un slug vers son fichier — UNIQUEMENT si l'utilisateur y a droit.
     * @return array{file:string,title:string}|null
     */
    public static function resolve(string $slug, ?array $user): ?array
    {
        foreach (self::build($user) as $entry) {
            if ($entry['slug'] === $slug) {
                return ['file' => $entry['file'], 'title' => $entry['title']];
            }
        }
        return null;
    }

    /**
     * Construit la liste plate des docs autorisés.
     * @return list<array{slug:string,title:string,group:string,file:string}>
     */
    private static function build(?array $user): array
    {
        if ($user === null) {
            return [];
        }
        $isAppAdmin = in_array(ROLE_APP_ADMIN, $user['roles'] ?? [], true);
        $entries = [];

        // 1) Documentation générale
        foreach (self::GENERAL as $basename => $cat) {
            $allowed = ($cat === 'admin') ? $isAppAdmin : true; // user ⇒ connecté
            if (!$allowed) {
                continue;
            }
            $file = ROOT_PATH . '/docs/' . $basename;
            if (($e = self::makeEntry('general', $cat === 'admin' ? 'general_admin' : 'general_user', $file)) !== null) {
                $entries[] = $e;
            }
        }

        // 2) Documentation des modules (déclarée dans chaque module.php → clé 'docs')
        $registry = Module::getInstance();
        foreach ($registry->getAll() as $name => $descriptor) {
            foreach ($descriptor['docs'] ?? [] as $doc) {
                $cat  = $doc['category'] ?? 'user';
                $file = $doc['file'] ?? '';
                if ($cat === 'admin') {
                    if (!$registry->canAccessAsAdmin($name, $user)) continue;
                    $group = 'module_admin';
                } else {
                    if (!$registry->canAccess($name, $user)) continue;
                    $group = 'module_user';
                }
                if (($e = self::makeEntry($name, $group, $file)) !== null) {
                    $entries[] = $e;
                }
            }
        }

        return $entries;
    }

    /**
     * Toutes les paires (scope, fichier) déclarées — SANS filtre d'accès.
     * Sert à construire la table de réécriture des liens internes.
     * @return list<array{scope:string,file:string}>
     */
    private static function allSources(): array
    {
        $sources = [];
        foreach (array_keys(self::GENERAL) as $basename) {
            $sources[] = ['scope' => 'general', 'file' => ROOT_PATH . '/docs/' . $basename];
        }
        foreach (Module::getInstance()->getAll() as $name => $descriptor) {
            foreach ($descriptor['docs'] ?? [] as $doc) {
                if (!empty($doc['file'])) {
                    $sources[] = ['scope' => $name, 'file' => $doc['file']];
                }
            }
        }
        return $sources;
    }

    /**
     * Carte realpath(fichier) => slug, sur TOUS les docs (sans filtre d'accès).
     * Mémoïsée pour la durée de la requête.
     * @return array<string,string>
     */
    private static function pathSlugMap(): array
    {
        static $map = null;
        if ($map !== null) {
            return $map;
        }
        $map = [];
        foreach (self::allSources() as $s) {
            $rp = realpath($s['file']);
            if ($rp !== false) {
                $map[$rp] = $s['scope'] . '--' . basename($s['file'], '.md');
            }
        }
        return $map;
    }

    /**
     * Réécrit, dans le HTML rendu d'un document, les liens relatifs :
     *   - liens `.md` internes  → route `/doc/<slug>` (ancre préservée) ;
     *   - images relatives      → route `/doc/asset/<currentSlug>?f=<chemin>`.
     * Les URL absolues/externes (http, mailto, //, /, #, data:) sont ignorées.
     */
    public static function rewriteLinks(string $html, string $sourceFile, string $currentSlug): string
    {
        $map     = self::pathSlugMap();
        $baseDir = dirname($sourceFile);

        // 1) Liens <a href="…">  →  /doc/<slug>
        $html = preg_replace_callback('/href="([^"]*)"/i', static function (array $m) use ($map, $baseDir): string {
            $href = $m[1];
            if ($href === '' || preg_match('#^(https?:|mailto:|//|/|#)#i', $href)) {
                return $m[0];
            }
            $frag = '';
            if (($pos = strpos($href, '#')) !== false) {
                $frag = substr($href, $pos);     // inclut le '#'
                $href = substr($href, 0, $pos);
            }
            if ($href === '' || !preg_match('/\.md$/i', $href)) {
                return $m[0];
            }
            $rp = realpath($baseDir . '/' . $href);
            if ($rp !== false && isset($map[$rp])) {
                return 'href="/doc/' . $map[$rp] . $frag . '"';
            }
            return $m[0];
        }, $html);

        // 2) Images <img src="…">  →  /doc/asset/<currentSlug>?f=…
        $html = preg_replace_callback('/src="([^"]*)"/i', static function (array $m) use ($currentSlug): string {
            $src = $m[1];
            if ($src === '' || preg_match('#^(https?:|//|/|data:)#i', $src)) {
                return $m[0];
            }
            if (!preg_match('/\.(png|jpe?g|gif|webp|svg)$/i', $src)) {
                return $m[0];
            }
            return 'src="/doc/asset/' . rawurlencode($currentSlug) . '?f=' . rawurlencode($src) . '"';
        }, $html);

        return $html;
    }

    /**
     * Fabrique une entrée si le fichier existe et est lisible.
     * @return array{slug:string,title:string,group:string,file:string}|null
     */
    private static function makeEntry(string $scope, string $group, string $file): ?array
    {
        if ($file === '' || !is_file($file) || !is_readable($file)) {
            return null;
        }
        $basename = basename($file, '.md');
        $content  = (string)file_get_contents($file);
        return [
            'slug'  => $scope . '--' . $basename,
            'title' => Markdown::extractTitle($content, $basename),
            'group' => $group,
            'file'  => $file,
        ];
    }
}
