<?php
/**
 * core/Router.php
 * Enrutador central del ERP.
 *
 * Mapea la URL entrante a modules/{m}/views/{v}.php.
 * Soporta rutas limpias (/turnero/dashboard) y parámetros explícitos (?m=&v=).
 *
 * RUTAS PÚBLICAS (sin autenticación):
 *   turnero/display  → pantalla TV para sala de espera
 *   turnero/kiosko   → pantalla táctil para tomar turno
 */

class Router
{
    /** Módulo por defecto cuando no se especifica ?m= */
    private const DEFAULT_MODULE = 'dashboard';

    /** Vista por defecto cuando no se especifica ?v= */
    private const DEFAULT_VIEW = 'index';

    /** Rutas que NO requieren sesión iniciada. Formato: 'modulo/vista' */
    private const PUBLIC_ROUTES = [
        'turnero/display',
        'turnero/kiosko',
        // Tablet de firma del paciente: la manipula el público y nadie va a
        // iniciar sesión en ella cada mañana. No queda abierta: se identifica
        // por la cookie del dispositivo y sin ella no muestra dato alguno.
        'turnero/firma',
        // Prueba de voces: hay que abrirla EN el televisor para saber qué voces
        // tiene ese equipo, y allí no hay sesión iniciada. No expone nada: solo
        // lista las voces del navegador y lee una frase de ejemplo inventada.
        'turnero/voces',
    ];

    /** Patrón permitido para módulo y vista: solo letras, números y guión bajo */
    private const SLUG_PATTERN = '/^[a-zA-Z0-9_]{1,64}$/';

    private string $module;
    private string $view;

    // ─── Constructor ────────────────────────────────────────────────────────

    public function __construct()
    {
        [$this->module, $this->view] = $this->resolveRoute();
    }

    // ─── API pública ────────────────────────────────────────────────────────

    public function getModule(): string { return $this->module; }
    public function getView(): string   { return $this->view; }

    /**
     * Indica si la ruta actual es pública (no requiere login).
     */
    public function isPublic(): bool
    {
        return in_array($this->module . '/' . $this->view, self::PUBLIC_ROUTES, true);
    }

    /**
     * Resuelve la ruta y devuelve el archivo de vista correspondiente.
     * Lanza una excepción si el archivo no existe.
     *
     * @return string Ruta absoluta al archivo de vista.
     * @throws RuntimeException Si el módulo/vista no se encuentran.
     */
    public function resolveFile(): string
    {
        $file = APP_ROOT . "/modules/{$this->module}/views/{$this->view}.php";

        if (!file_exists($file)) {
            throw new RuntimeException(
                "Vista no encontrada: {$this->module}/{$this->view}",
                404
            );
        }

        return $file;
    }

    // ─── Internos ───────────────────────────────────────────────────────────

    /**
     * Determina módulo y vista a partir de la URL.
     * Prioridad: PATH_INFO limpio → parámetros GET ?m= ?v=
     *
     * @return array{string, string}
     */
    private function resolveRoute(): array
    {
        // 1. Intentar ruta limpia desde PATH_INFO o REQUEST_URI
        //    Formato: /modulo/vista  →  e.g. /turnero/dashboard
        $path = $this->cleanPath();
        if ($path !== null) {
            [$m, $v] = $path;
            if ($this->isValidSlug($m) && $this->isValidSlug($v)) {
                return [$m, $v];
            }
        }

        // 2. Parámetros GET ?m=module&v=view
        $m = $_GET['m'] ?? self::DEFAULT_MODULE;
        $v = $_GET['v'] ?? self::DEFAULT_VIEW;

        // Sanitizar: solo caracteres seguros
        $m = preg_replace('/[^a-zA-Z0-9_]/', '', $m);
        $v = preg_replace('/[^a-zA-Z0-9_]/', '', $v);

        if (!$this->isValidSlug($m)) $m = self::DEFAULT_MODULE;
        if (!$this->isValidSlug($v)) $v = self::DEFAULT_VIEW;

        return [$m, $v];
    }

    /**
     * Extrae módulo y vista de la REQUEST_URI limpia.
     * Retorna null si la URI no corresponde al formato /m/v.
     *
     * @return array{string,string}|null
     */
    private function cleanPath(): ?array
    {
        $uri = $_SERVER['REQUEST_URI'] ?? '';

        // Eliminar query string
        $pos = strpos($uri, '?');
        if ($pos !== false) {
            $uri = substr($uri, 0, $pos);
        }

        // Eliminar el subdirectorio base si el proyecto no está en la raíz
        $scriptDir = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/');
        if ($scriptDir !== '' && str_starts_with($uri, $scriptDir)) {
            $uri = substr($uri, strlen($scriptDir));
        }

        // Normalizar y dividir
        $parts = array_values(array_filter(explode('/', trim($uri, '/'))));

        if (count($parts) >= 2) {
            return [$parts[0], $parts[1]];
        }

        return null;
    }

    private function isValidSlug(string $slug): bool
    {
        return (bool) preg_match(self::SLUG_PATTERN, $slug);
    }
}
