<?php

declare(strict_types=1);

class Router
{
    private static array $routes = [];
    private static array $currentParams = [];
    private static string $langPrefix = '';
    private static string $typePrefix = '';
    private static string $typeCode = '';
    private static array $alternates = [];
    private static array $noPrefixPaths = [];

     
    private const TRANSLATABLE_SLUGS = [
        'categories' => 'categories', 'category' => 'category',
        'tags' => 'tags', 'tag' => 'tag',
        'videos' => 'videos', 'video' => 'video',
        'pornstars' => 'pornstars', 'pornstar' => 'pornstar',
        'channels' => 'channels', 'channel' => 'channel',
        'search' => 'search', 'favorites' => 'favorites',
        'watch_history' => 'watch-history',
        'login' => 'login', 'register' => 'register',
        'profile' => 'profile', 'embed' => 'embed',
    ];
    private static ?array $_slugReverseMap = null;

    public static function get(string $path, callable $handler): void
    {
        self::$routes['GET'][$path] = $handler;
    }

    public static function post(string $path, callable $handler): void
    {
        self::$routes['POST'][$path] = $handler;
    }

    

    public static function addNoPrefixPath(string $path): void
    {
        self::$noPrefixPaths[] = $path;
    }

    public static function dispatch(): void
    {
        $method = $_SERVER['REQUEST_METHOD'];
         
         
        if ($method === 'HEAD') { $method = 'GET'; }
        $path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
        $path = rtrim((string) ($path ?? '/'), '/') ?: '/';

         
        if (!str_starts_with($path, '/admin') && !str_starts_with($path, '/install')) {
            if (preg_match('#^/([a-z]{2})(/.*|$)#', $path, $m)) {
                $code = $m[1];
                $remaining = $m[2] ?: '/';
                $remaining = rtrim($remaining, '/') ?: '/';
                if (Translator::isEnabled($code)
                    && !str_starts_with($remaining, '/admin')
                    && !str_starts_with($remaining, '/install')
                ) {
                    $defaultCode = Translator::getDefaultCode();
                    if ($code === $defaultCode) {
                         
                        $qs = $_SERVER['QUERY_STRING'] ?? '';
                        header('Location: ' . $remaining . ($qs ? '?' . $qs : ''), true, 301);
                        exit;
                    }
                     
                    setcookie('_tp_lang', $code, [
                        'expires' => time() + 365 * 86400,
                        'path' => '/',
                        'httponly' => true,
                        'secure' => !empty($_SERVER['HTTPS']),
                        'samesite' => 'Lax',
                    ]);
                    Translator::setLocale($code);
                    self::$langPrefix = '/' . $code;
                    $path = $remaining;
                }
            }
        }

         
        if (self::$langPrefix === ''
            && !str_starts_with($path, '/admin')
            && !str_starts_with($path, '/install')
            && $method === 'GET'
            && empty($_SERVER['HTTP_X_REQUESTED_WITH'])
            && (empty($_SERVER['HTTP_ACCEPT']) || !str_contains($_SERVER['HTTP_ACCEPT'], 'application/json'))
            && !self::isCrawler()  
        ) {
            $currentLocale = Translator::locale();
            $defaultCode = Translator::getDefaultCode();
            if ($currentLocale !== $defaultCode) {
                $qs = $_SERVER['QUERY_STRING'] ?? '';
                header('Location: /' . $currentLocale . $path . ($qs ? '?' . $qs : ''), true, 302);
                exit;
            }
        }

         
        if (!str_starts_with($path, '/admin')
            && !str_starts_with($path, '/install')
            && !str_starts_with($path, '/sitemap')
        ) {
            $types = Video::availableTypes();
            if (count($types) >= 2) {
                $defaultType = $types[0];  
                $defaultSlug = Video::typeSlug($defaultType);

                 
                foreach ($types as $type) {
                    if ($type === $defaultType) continue;
                    $slug = Video::typeSlug($type);
                    if (str_starts_with($path, '/' . $slug . '/') || $path === '/' . $slug) {
                        self::$typeCode = $type;
                        self::$typePrefix = '/' . $slug;
                        $path = substr($path, strlen('/' . $slug)) ?: '/';
                        $path = rtrim($path, '/') ?: '/';
                         
                        setcookie('tp_video_type', $type, [
                            'expires' => time() + 365 * 86400,
                            'path' => '/',
                            'httponly' => true,
                            'secure' => !empty($_SERVER['HTTPS']),
                            'samesite' => 'Lax',
                        ]);
                        break;
                    }
                }

                 
                if (self::$typeCode === ''
                    && (str_starts_with($path, '/' . $defaultSlug . '/') || $path === '/' . $defaultSlug)
                ) {
                    $stripped = substr($path, strlen('/' . $defaultSlug)) ?: '/';
                    $stripped = rtrim($stripped, '/') ?: '/';
                    $qs = $_SERVER['QUERY_STRING'] ?? '';
                    header('Location: ' . self::$langPrefix . $stripped . ($qs ? '?' . $qs : ''), true, 301);
                    exit;
                }

                 
                if (self::$typeCode === '') {
                    $oldSlugsJson = Setting::get('video_type_old_slugs', '');
                    $oldSlugs = ($oldSlugsJson !== '') ? (json_decode($oldSlugsJson, true) ?: []) : [];
                    foreach ($oldSlugs as $oldSlug => $code) {
                        if (str_starts_with($path, '/' . $oldSlug . '/') || $path === '/' . $oldSlug) {
                            $remainder = substr($path, strlen('/' . $oldSlug)) ?: '/';
                            $remainder = rtrim($remainder, '/') ?: '/';
                            $newSlug = Video::typeSlug($code);
                            $newPrefix = ($code !== $defaultType) ? '/' . $newSlug : '';
                            $qs = $_SERVER['QUERY_STRING'] ?? '';
                            header('Location: ' . self::$langPrefix . $newPrefix . $remainder . ($qs ? '?' . $qs : ''), true, 301);
                            exit;
                        }
                    }
                }

                 
                if (self::$typeCode === '') {
                    self::$typeCode = $defaultType;
                }
            }
        }

         
         
         
         
        $candidates = [$path];
        if (!str_starts_with($path, '/admin') && !str_starts_with($path, '/install')) {
            $candidates = self::resolveTranslatedSlugCandidates($path);
        }

         
        foreach ($candidates as $candidate) {
            if (isset(self::$routes[$method][$candidate])) {
                HookSystem::doAction('router.before_dispatch', $candidate);
                (self::$routes[$method][$candidate])();
                return;
            }
        }

         
        foreach ($candidates as $candidate) {
            foreach (self::$routes[$method] ?? [] as $pattern => $handler) {
                if (str_contains($pattern, '{')) {
                    $regex = self::patternToRegex($pattern);
                    if (preg_match($regex, $candidate, $matches)) {
                        self::$currentParams = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                        HookSystem::doAction('router.before_dispatch', $candidate);
                        $handler();
                        return;
                    }
                }
            }
        }

         
        http_response_code(404);
        if (App::isInstalled()) {
            ThemeRenderer::render('404');
        } else {
            echo '<h1>404 Not Found</h1>';
        }
    }

    private static function patternToRegex(string $pattern): string
    {
        $regex = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $pattern);
        return '#^' . $regex . '$#';
    }

    public static function param(string $name, string $default = ''): string
    {
        return self::$currentParams[$name] ?? $default;
    }

    public static function render(string $template, array $data = []): void
    {
        extract($data, EXTR_SKIP);
        require APP_ROOT . '/templates/' . $template . '.php';
    }

    public static function redirect(string $url): never
    {
         
        if (str_starts_with($url, '/')
            && !str_starts_with($url, '/admin')
            && !str_starts_with($url, '/install')
            && !self::isNoPrefixPath($url)
        ) {
            $fullPrefix = self::$langPrefix . self::$typePrefix;
            if ($fullPrefix !== '' && !str_starts_with($url, $fullPrefix)) {
                $url = $fullPrefix . $url;
            }
        }
        header('Location: ' . $url);
        exit;
    }

    public static function json(array $data, int $code = 200): never
    {
        http_response_code($code);
        header('Content-Type: application/json');
        echo json_encode($data);
        exit;
    }

    

    public static function url(string $path): string
    {
        if (str_starts_with($path, '/')
            && !str_starts_with($path, '/admin')
            && !str_starts_with($path, '/install')
            && !self::isNoPrefixPath($path)
        ) {
            $path = self::translateSlugsInPath($path);
            return self::$langPrefix . self::$typePrefix . $path;
        }
        return $path;
    }

    

    private static function resolveTranslatedSlugCandidates(string $path): array
    {
        if (Translator::locale() === Translator::getDefaultCode()) {
            return [$path];
        }

        $map = self::getSlugReverseMap();
        if (empty($map)) return [$path];

        $segments = explode('/', trim($path, '/'));
        if (empty($segments) || ($segments === [''])) return [$path];

         
         
        $maxResolve = min(2, count($segments));
        $options = [];
        foreach ($segments as $i => $segment) {
            if ($i < $maxResolve && isset($map[$segment])) {
                $opts = $map[$segment];
                if (!in_array($segment, $opts, true)) {
                    $opts[] = $segment;
                }
                $options[$i] = $opts;
            } else {
                $options[$i] = [$segment];
            }
        }

         
         
         
        if (count($segments) >= 2
            && in_array('videos', $options[0], true)
            && in_array('video', $options[0], true)
        ) {
            $preferred = self::isVideoListFilterSlug($segments[1]) ? 'videos' : 'video';
            $idx = array_search($preferred, $options[0], true);
            if ($idx !== false) {
                unset($options[0][$idx]);
                array_unshift($options[0], $preferred);
                $options[0] = array_values($options[0]);
            }
        }

         
        $candidates = [''];
        foreach ($options as $opts) {
            $next = [];
            foreach ($candidates as $base) {
                foreach ($opts as $opt) {
                    $next[] = $base . '/' . $opt;
                }
            }
            $candidates = $next;
        }

        return array_values(array_unique($candidates));
    }

    

    private static function isVideoListFilterSlug(string $segment): bool
    {
        static $keys = [
            'slug_new_videos'     => 'new_videos',
            'slug_top_rated'      => 'top_rated',
            'slug_most_viewed'    => 'most_viewed',
            'slug_most_favorited' => 'most_favorited',
        ];
        $en = Translator::getFileStrings('en');
        foreach ($keys as $slugKey => $labelKey) {
            if ($segment === Translator::slug($slugKey, $labelKey) || $segment === ($en[$slugKey] ?? '')) {
                return true;
            }
        }
        return false;
    }

    

    private static function translateSlugsInPath(string $path): string
    {
        if (Translator::locale() === Translator::getDefaultCode()) {
            return $path;
        }

        $segments = explode('/', trim($path, '/'));
        if (empty($segments) || ($segments === [''])) return $path;

        $changed = false;
        $maxTranslate = min(2, count($segments));
        for ($i = 0; $i < $maxTranslate; $i++) {
            $key = array_search($segments[$i], self::TRANSLATABLE_SLUGS, true);
            if ($key !== false) {
                $translated = Translator::slug('slug_' . $key, 'slug_' . $key);
                if ($translated !== $segments[$i]) {
                    $segments[$i] = $translated;
                    $changed = true;
                }
            }
        }

        return $changed ? '/' . implode('/', $segments) : $path;
    }

    

    private static function getSlugReverseMap(): array
    {
        if (self::$_slugReverseMap !== null) return self::$_slugReverseMap;

        self::$_slugReverseMap = [];
        foreach (self::TRANSLATABLE_SLUGS as $key => $english) {
            $translated = Translator::slug('slug_' . $key, 'slug_' . $key);
            if ($translated !== $english) {
                self::$_slugReverseMap[$translated][] = $english;
            }
        }
        return self::$_slugReverseMap;
    }

    

    private static function isNoPrefixPath(string $url): bool
    {
        foreach (self::$noPrefixPaths as $prefix) {
            if (str_starts_with($url, $prefix)) {
                return true;
            }
        }
        return false;
    }

    

    public static function langPrefix(): string
    {
        return self::$langPrefix;
    }

    

    public static function typePrefix(): string
    {
        return self::$typePrefix;
    }

    

    public static function typeCode(): string
    {
        return self::$typeCode;
    }

    

    public static function setAlternates(array $alternates): void
    {
        self::$alternates = $alternates;
    }

    

    public static function switchLangUrl(string $code): string
    {
        $defaultCode = Translator::getDefaultCode();

         
        if (!empty(self::$alternates[$code])) {
            $url = self::$alternates[$code];
             
            $parsed = parse_url($url, PHP_URL_PATH);
            if ($parsed) {
                $url = $parsed;
            }
            if ($code === $defaultCode) {
                return $url . '?_lang=' . $code;
            }
            return $url;
        }

        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $path = rtrim($path, '/') ?: '/';

         
        if (self::$langPrefix !== '' && str_starts_with($path, self::$langPrefix)) {
            $path = substr($path, strlen(self::$langPrefix)) ?: '/';
        }

        $qs = $_SERVER['QUERY_STRING'] ?? '';
        if ($qs) {
            parse_str($qs, $params);
            unset($params['_lang']);
            $qs = http_build_query($params);
        }

        if ($code === $defaultCode) {
             
            return $path . '?_lang=' . $code . ($qs ? '&' . $qs : '');
        }

         
         
        return '/' . $code . $path . ($qs ? '?' . $qs : '');
    }

    

    public static function switchTypeUrl(string $type): string
    {
        $types = Video::availableTypes();
        $defaultType = $types[0] ?? Video::DEFAULT_TYPE;

        $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $path = rtrim($path, '/') ?: '/';

         
        if (self::$langPrefix !== '' && str_starts_with($path, self::$langPrefix)) {
            $path = substr($path, strlen(self::$langPrefix)) ?: '/';
        }

         
        if (self::$typePrefix !== '' && str_starts_with($path, self::$typePrefix)) {
            $path = substr($path, strlen(self::$typePrefix)) ?: '/';
        }

        $qs = $_SERVER['QUERY_STRING'] ?? '';
        if ($qs) {
            parse_str($qs, $params);
            unset($params['page']);
            $qs = http_build_query($params);
        }

        $newTypePrefix = ($type !== $defaultType) ? '/' . Video::typeSlug($type) : '';
        return self::$langPrefix . $newTypePrefix . $path . ($qs ? '?' . $qs : '');
    }

    

     
    public static function isCrawler(): bool
    {
        $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
        return $ua === '' || preg_match('/bot|crawl|spider|slurp|facebookexternalhit|mediapartners|baiduspider|yandex|sogou|bytespider|googlebot|bingbot/i', $ua) === 1;
    }

    public static function typePrefixFor(string $videoType): string
    {
        $types = Video::availableTypes();
        if (count($types) < 2) {
            return '';
        }
        $defaultType = $types[0] ?? Video::DEFAULT_TYPE;
        return ($videoType !== $defaultType) ? '/' . Video::typeSlug($videoType) : '';
    }

    

    public static function enforceCanonicalType(string $videoType, string $routeSegment, string $slug): void
    {
        $correct = self::typePrefixFor($videoType);
        if (self::$typePrefix !== $correct) {
            $qs = $_SERVER['QUERY_STRING'] ?? '';
            header('Location: ' . self::$langPrefix . $correct . '/' . $routeSegment . '/' . $slug . ($qs ? '?' . $qs : ''), true, 301);
            exit;
        }
    }



    public static function enforceCanonicalSlug(string $routeSegment, string $canonicalSlug): void
    {
        if ($canonicalSlug === '') {
            return;
        }


        $requested = rawurldecode(self::param('slug'));
        if ($requested === '' || $requested === $canonicalSlug) {
            return;
        }
        $qs = $_SERVER['QUERY_STRING'] ?? '';
        header('Location: ' . self::$langPrefix . self::$typePrefix . '/' . $routeSegment . '/' . $canonicalSlug . ($qs ? '?' . $qs : ''), true, 301);
        exit;
    }

    public static function csrfToken(): string
    {
        if (empty($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }

    public static function verifyCsrf(): bool
    {
        $session = $_SESSION['csrf_token'] ?? '';
        $token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
        return $token !== '' && hash_equals($session, $token);
    }

    public static function csrfField(): string
    {
        return '<input type="hidden" name="csrf_token" value="' . self::csrfToken() . '">';
    }
}
