<?php

declare(strict_types=1);



class SeoManager
{
    private static bool $booted = false;

     
    public const DEFAULTS = [
         
        'seo_title_separator'       => ' - ',
        'seo_home_title'            => '',
        'seo_pattern_video'         => '{title} {sep} {site_name}',
        'seo_pattern_category'      => '{name} {sep} {site_name}',
        'seo_pattern_tag'           => '{name} {sep} {site_name}',
        'seo_pattern_performer'     => '{name} {sep} {site_name}',
        'seo_pattern_channel'        => '{name} {sep} {site_name}',
        'seo_pattern_search'        => 'Search: {query} {sep} {site_name}',
        'seo_pattern_page'          => '{title} {sep} {site_name}',
         
        'seo_home_meta_description' => '',
        'seo_desc_video'            => 'Watch {title} online. {description_short}',
        'seo_desc_category'         => 'Browse {count} videos in {name}. {description_short}',
        'seo_desc_tag'              => 'Videos tagged {name}. {count} videos available on {site_name}.',
        'seo_desc_performer'        => '{name} videos. {bio_short} Watch {count} videos on {site_name}.',
        'seo_desc_channel'           => '{name} videos from {site_name}. {description_short}',
        'seo_desc_search'           => 'Search results for {query} on {site_name}. {count} videos found.',
        'seo_desc_page'             => '{excerpt}',
         
        'seo_pattern_album_listing' => 'Photo Albums {sep} {site_name}',
        'seo_pattern_album'         => '{title} {sep} {site_name}',
        'seo_desc_album_listing'    => 'Browse {count} photo albums and image galleries on {site_name}.',
        'seo_desc_album'            => '{title} — {count} photos. {description_short}',
        'seo_sitemap_albums'        => '1',
        'seo_noindex_albums'        => '0',
        'seo_pattern_album_category'  => '{name} {sep} {site_name}',
        'seo_pattern_album_tag'       => '{name} {sep} {site_name}',
        'seo_pattern_album_performer' => '{name} {sep} {site_name}',
        'seo_pattern_album_channel'   => '{name} {sep} {site_name}',
        'seo_desc_album_category'     => 'Browse {count} albums in {name}. {description_short}',
         
        'seo_pagination_suffix'     => ' - Page {page}',
        'seo_pagination_title'      => '1',
         
        'seo_og_image'              => '',
        'seo_og_auto_ctr'           => '1',
        'seo_twitter_card'          => 'summary_large_image',
        'seo_twitter_handle'        => '',
        'seo_fb_app_id'             => '',
         
        'seo_sitemap_videos'        => '1',
        'seo_sitemap_categories'    => '1',
        'seo_sitemap_tags'          => '1',
        'seo_sitemap_performers'    => '1',
        'seo_sitemap_channels'       => '1',
        'seo_sitemap_pages'         => '1',
        'seo_sitemap_max_urls'      => '25000',
        'seo_sitemap_video_tags'    => '1',
        'seo_sitemap_image_tags'    => '1',
        'seo_sitemap_hreflang'      => '1',
         
        'seo_noindex_entire_site'   => '0',
        'seo_noindex_tags'          => '0',
        'seo_noindex_categories'    => '0',
        'seo_noindex_performers'    => '0',
        'seo_noindex_channels'       => '0',
        'seo_noindex_search'        => '1',
        'seo_noindex_paginated'     => '0',
        'seo_taxonomy_min_items'    => '0',
        'seo_taxonomy_keep_described' => '1',
         
        'seo_robots_txt'            => '',
         
        'seo_google_verify'         => '',
        'seo_bing_verify'           => '',
        'seo_pinterest_verify'      => '',
        'seo_yandex_verify'         => '',
        'seo_custom_head'           => '',
        'seo_custom_body'           => '',
         
        'seo_ga4_id'                => '',
         
        'seo_type_label_hetero'     => 'Straight',
        'seo_type_label_gay'        => 'Gay',
        'seo_type_label_hentai'     => 'Hentai',
    ];

    public const TAXONOMY_INDEX_TYPES = [
        'category' => [
            'label' => 'Categories', 'route' => '/category/', 'table' => 'categories',
            'count' => 'video_count', 'desc' => 'description', 'data' => 'category',
            'sitemap' => 'categories', 'noindex' => 'seo_noindex_categories',
        ],
        'tag' => [
            'label' => 'Tags', 'route' => '/tag/', 'table' => 'tags',
            'count' => 'video_count', 'desc' => '', 'data' => 'tag',
            'sitemap' => 'tags', 'noindex' => 'seo_noindex_tags',
        ],
        'performer' => [
            'label' => 'Pornstars', 'route' => '/pornstar/', 'table' => 'performers',
            'count' => 'video_count', 'desc' => 'bio', 'data' => 'performer',
            'sitemap' => 'performers', 'noindex' => 'seo_noindex_performers',
        ],
        'channel' => [
            'label' => 'Channels', 'route' => '/channel/', 'table' => 'channels',
            'count' => 'video_count', 'desc' => 'description', 'data' => 'channel',
            'sitemap' => 'channels', 'noindex' => 'seo_noindex_channels',
        ],
        'album-category' => [
            'label' => 'Album categories', 'route' => '/album-category/', 'table' => 'album_categories',
            'count' => 'album_count', 'desc' => 'description', 'data' => 'category',
            'sitemap' => 'album-categories', 'noindex' => 'seo_noindex_albums',
        ],
        'album-tag' => [
            'label' => 'Album tags', 'route' => '/album-tag/', 'table' => 'album_tags',
            'count' => 'album_count', 'desc' => '', 'data' => 'tag',
            'sitemap' => 'album-tags', 'noindex' => 'seo_noindex_albums',
        ],
        'album-pornstar' => [
            'label' => 'Album pornstars', 'route' => '/album-pornstar/', 'table' => 'album_performers',
            'count' => 'album_count', 'desc' => 'bio', 'data' => 'performer',
            'sitemap' => 'album-performers', 'noindex' => 'seo_noindex_albums',
        ],
        'album-channel' => [
            'label' => 'Album channels', 'route' => '/album-channel/', 'table' => 'album_channels',
            'count' => 'album_count', 'desc' => 'description', 'data' => 'channel',
            'sitemap' => 'album-channels', 'noindex' => 'seo_noindex_albums',
        ],
    ];

    public const TAXONOMY_DESCRIPTION_MIN_CHARS = 50;

    private static bool $thinTaxonomy = false;

    

    public static function boot(): void
    {
        if (self::$booted) return;
        self::$booted = true;

         
        self::seedDefaults();

         
        HookSystem::addFilter('seo.title_separator', function (string $sep): string {
            $custom = Setting::get('seo_title_separator', '');
            return $custom !== '' ? $custom : $sep;
        });

         
        HookSystem::addFilter('theme.template_data', function (array $data, string $template): array {
            return self::applyTitlePatterns($data, $template);
        }, 5);

        HookSystem::addFilter('theme.template_data', function (array $data, string $template): array {
            self::$thinTaxonomy = self::detectThinTaxonomy($data, $template);
            return $data;
        }, 6);

         
        Branding::register();

        HookSystem::addAction('head.meta', function (): void {
            self::renderHeadMeta();
        });

         
        HookSystem::addFilter('footer.scripts', function (string $html): string {
            $custom = Setting::get('seo_custom_body', '');
            if ($custom !== '') {
                $html .= "\n" . $custom . "\n";
            }
            return $html;
        });

         
        HookSystem::addFilter('sitemap.skip_entity', function (bool $skip, string $entityType): bool {
            $key = 'seo_sitemap_' . $entityType;
            return Setting::get($key, '1') !== '1';
        });

         
        HookSystem::addFilter('sitemap.options', function (array $opts): array {
            $opts['video_tags']  = Setting::get('seo_sitemap_video_tags', '1') === '1';
            $opts['image_tags']  = Setting::get('seo_sitemap_image_tags', '1') === '1';
            $opts['hreflang']    = Setting::get('seo_sitemap_hreflang', '1') === '1';
            $opts['max_urls']    = max(1000, min(50000, (int) Setting::get('seo_sitemap_max_urls', '25000')));
            return $opts;
        });

         
        HookSystem::addFilter('sitemap.robots_txt', function (string $content): string {
            $custom = Setting::get('seo_robots_txt', '');
            return $custom !== '' ? $custom : $content;
        });
    }

    

    private static function seedDefaults(): void
    {
        foreach (self::DEFAULTS as $key => $default) {
            $existing = Setting::get($key, "\0");
            if ($existing === "\0") {
                Setting::set($key, $default);
            }
        }
    }

     

    public static function applyTitlePatterns(array $data, string $template): array
    {
        $siteName = Branding::siteName();
        $siteDesc = Branding::siteDescription();
        $sep = Setting::get('seo_title_separator', ' - ');
        $page = max(1, (int) ($_GET['page'] ?? 1));

         
        $activeType = Video::activeType();
        $typeLabel  = '';
        if ($activeType !== '') {
            $typeLabel = Setting::get('seo_type_label_' . $activeType, ucfirst($activeType));
        }

        $vars = [
            '{site_name}'        => $siteName,
            '{site_description}' => $siteDesc,
            '{sep}'              => $sep,
            '{type}'             => $typeLabel,
        ];

         
        if ($template === 'home' || $template === 'index') {
            $homeTitle = Branding::homeTitle();
            $homeMeta = Branding::homeDescription();

            if (str_contains($homeTitle, '{count}') || str_contains($homeMeta, '{count}')) {
                $vars['{count}'] = number_format(Video::count(['status' => 'published']));
            }

            if ($homeTitle !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($homeTitle, $vars));
            }
            if ($homeMeta !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($homeMeta, $vars);
            }
        }

         
        if ($template === 'video' || $template === 'watch') {
            $videoTitle = strip_tags($data['video']['title'] ?? $data['pageTitle'] ?? '');
            $videoDescRaw = $data['video']['description'] ?? '';
            $videoDescClean = preg_replace('/\s+/', ' ', trim(strip_tags($videoDescRaw)));

             
            $pornstars  = self::extractNames($data['performers'] ?? $data['video']['performers'] ?? []);
            $categories = self::extractNames($data['categories'] ?? $data['video']['categories'] ?? []);
            $tagList    = self::extractNames($data['tags'] ?? $data['video']['tags'] ?? []);
            $channels    = self::extractNames($data['video']['channels'] ?? []);

             
            $firstCategory = '';
            $cats = $data['categories'] ?? $data['video']['categories'] ?? [];
            if (is_array($cats) && !empty($cats)) {
                $firstCategory = strip_tags($cats[0]['name'] ?? '');
            }

             
            $vars['{title}']             = $videoTitle;
            $vars['{description}']       = $videoDescClean;
            $vars['{description_short}'] = self::shortText($videoDescRaw, 100);
            $vars['{pornstars}']         = $pornstars;
            $vars['{categories}']        = $categories;
            $vars['{tags}']              = $tagList;
            $vars['{channels}']           = $channels;
            $vars['{category}']          = $firstCategory;
            $vars['{duration}']          = isset($data['video']['duration']) ? Format::duration((int) $data['video']['duration']) : '';
            $vars['{views}']             = isset($data['video']['views']) ? Format::number((int) $data['video']['views']) : '';

            $pattern = Setting::get('seo_pattern_video', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }

            $descPattern = Setting::get('seo_desc_video', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $vars);
            }
        }

         
        if ($template === 'category') {
            $catName = strip_tags($data['category']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{name}'] = $catName;
            $vars['{count}'] = self::extractCount($data);

            $pattern = Setting::get('seo_pattern_category', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }

            $descVars = $vars;
            $catDescRaw = $data['category']['description'] ?? '';
            $descVars['{description}']       = preg_replace('/\s+/', ' ', trim(strip_tags($catDescRaw)));
            $descVars['{description_short}'] = self::shortText($catDescRaw, 100);
            $descPattern = Setting::get('seo_desc_category', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }

        if ($template === 'album-category') {
            $vars['{name}'] = strip_tags($data['category']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{count}'] = (string) (int) ($data['category']['album_count'] ?? 0);
            $pattern = Setting::get('seo_pattern_album_category', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }
            $descVars = $vars;
            $rawd = $data['category']['description'] ?? '';
            $descVars['{description}']       = preg_replace('/\s+/', ' ', trim(strip_tags($rawd)));
            $descVars['{description_short}'] = self::shortText($rawd, 100);
            $descPattern = Setting::get('seo_desc_album_category', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }
        if ($template === 'album-tag') {
            $vars['{name}'] = strip_tags($data['tag']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{count}'] = (string) (int) ($data['tag']['album_count'] ?? 0);
            $pattern = Setting::get('seo_pattern_album_tag', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }
        }
        if ($template === 'album-pornstar') {
            $vars['{name}'] = strip_tags($data['performer']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{count}'] = (string) (int) ($data['performer']['album_count'] ?? 0);
            $pattern = Setting::get('seo_pattern_album_performer', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }
        }
        if ($template === 'album-channel') {
            $vars['{name}'] = strip_tags($data['channel']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{count}'] = (string) (int) ($data['channel']['album_count'] ?? 0);
            $pattern = Setting::get('seo_pattern_album_channel', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }
        }

         
        if ($template === 'tag') {
            $tagName = strip_tags($data['tag']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{name}'] = $tagName;
            $vars['{count}'] = self::extractCount($data);

            $pattern = Setting::get('seo_pattern_tag', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }

            $descVars = $vars;
            $descPattern = Setting::get('seo_desc_tag', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }

         
        if ($template === 'performer') {
            $perfName = strip_tags($data['performer']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{name}'] = $perfName;
            $vars['{count}'] = self::extractCount($data);

            $pattern = Setting::get('seo_pattern_performer', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }

            $descVars = $vars;
            $bioRaw = $data['performer']['bio'] ?? '';
            $descVars['{bio}']       = preg_replace('/\s+/', ' ', trim(strip_tags($bioRaw)));
            $descVars['{bio_short}'] = self::shortText($bioRaw, 100);
            $descPattern = Setting::get('seo_desc_performer', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }

         
        if ($template === 'channel') {
            $channelName = strip_tags($data['channel']['name'] ?? $data['pageTitle'] ?? '');
            $vars['{name}'] = $channelName;
            $vars['{count}'] = self::extractCount($data);

            $pattern = Setting::get('seo_pattern_channel', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }

            $descVars = $vars;
            $channelDescRaw = $data['channel']['description'] ?? '';
            $descVars['{description}']       = preg_replace('/\s+/', ' ', trim(strip_tags($channelDescRaw)));
            $descVars['{description_short}'] = self::shortText($channelDescRaw, 100);
            $descPattern = Setting::get('seo_desc_channel', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }

         
        if ($template === 'search') {
            $query = strip_tags($data['query'] ?? $data['searchQuery'] ?? '');
            $vars['{query}'] = $query;
            $vars['{count}'] = self::extractCount($data);

            $pattern = Setting::get('seo_pattern_search', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }

            $descVars = $vars;
            $descPattern = Setting::get('seo_desc_search', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }

         
         
         
        if ($template === 'page') {
            $pageTitleText = strip_tags($data['page']['title'] ?? $data['pageTitle'] ?? '');
            $vars['{title}'] = $pageTitleText;

            $pageMetaTitle = trim($data['page']['meta_title'] ?? '');
            if ($pageMetaTitle !== '') {
                $data['pageTitle'] = $pageMetaTitle;
            } else {
                $pattern = Setting::get('seo_pattern_page', '');
                if ($pattern !== '') {
                    $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
                }
            }

            $descVars = $vars;
            $descVars['{excerpt}'] = self::shortText($data['page']['body'] ?? '', 150);

            $pageMetaDesc = trim($data['page']['meta_description'] ?? '');
            if ($pageMetaDesc !== '') {
                $data['metaDescription'] = $pageMetaDesc;
            } else {
                $descPattern = Setting::get('seo_desc_page', '');
                if ($descPattern !== '') {
                    $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
                }
            }
        }

         
        if ($template === 'albums') {
            $vars['{count}'] = self::extractCount($data);
            $pattern = Setting::get('seo_pattern_album_listing', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }
            $descPattern = Setting::get('seo_desc_album_listing', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $vars);
            }
        }

         
        if ($template === 'album') {
            $albTitle = strip_tags($data['album']['title'] ?? $data['pageTitle'] ?? '');
            $vars['{title}'] = $albTitle;
            $vars['{name}'] = $albTitle;
            $vars['{count}'] = (string) (int) ($data['album']['image_count'] ?? 0);
            $pattern = Setting::get('seo_pattern_album', '');
            if ($pattern !== '') {
                $data['pageTitle'] = self::cleanTitle(strtr($pattern, $vars));
            }
            $descVars = $vars;
            $albDescRaw = $data['album']['description'] ?? '';
            $descVars['{description}']       = preg_replace('/\s+/', ' ', trim(strip_tags($albDescRaw)));
            $descVars['{description_short}'] = self::shortText($albDescRaw, 100);
            $descPattern = Setting::get('seo_desc_album', '');
            if ($descPattern !== '') {
                $data['metaDescription'] = self::applyDescriptionPattern($descPattern, $descVars);
            }
        }

         
        if ($page > 1 && Setting::get('seo_pagination_title', '1') === '1') {
            $suffix = Setting::get('seo_pagination_suffix', ' - Page {page}');
            $suffix = str_replace('{page}', (string) $page, $suffix);
            $data['pageTitle'] = ($data['pageTitle'] ?? '') . $suffix;
        }

         
        if (!isset($data['ogTags'])) {
            $data['ogTags'] = [];
        }

        if (empty($data['ogTags']['og:image'])) {
            $ogImage = self::resolveOgImage($data, $template);
            if ($ogImage !== '') {
                $data['ogTags']['og:image'] = $ogImage;
            }
        }

         
        if (!isset($data['twitterCard'])) $data['twitterCard'] = [];
        $twitterHandle = Setting::get('seo_twitter_handle', '');
        if ($twitterHandle !== '') {
            $data['twitterCard']['twitter:site'] = $twitterHandle;
        }
         
        $existingCardType = $data['twitterCard']['twitter:card'] ?? '';
        if ($existingCardType !== 'player') {
            $twitterCardType = Setting::get('seo_twitter_card', 'summary_large_image');
            if ($twitterCardType !== '') {
                $data['twitterCard']['twitter:card'] = $twitterCardType;
            }
        }

         
        $fbAppId = Setting::get('seo_fb_app_id', '');
        if ($fbAppId !== '') {
            $data['ogTags']['fb:app_id'] = $fbAppId;
        }

         
        if (!empty($data['metaDescription']) && mb_strlen($data['metaDescription']) >= 40) {
            if (isset($data['ogTags']['og:description']) && mb_strlen($data['ogTags']['og:description']) < 40) {
                $data['ogTags']['og:description'] = $data['metaDescription'];
            }
            if (isset($data['twitterCard']['twitter:description']) && mb_strlen($data['twitterCard']['twitter:description']) < 40) {
                $data['twitterCard']['twitter:description'] = $data['metaDescription'];
            }
        }

        return $data;
    }

    

    private static function resolveOgImage(array $data, string $template): string
    {
        $siteUrl = SEO::siteUrl();

         
        if (Setting::get('seo_og_auto_ctr', '1') === '1' && PluginManager::isActive('ctr')) {
            $listingTemplates = ['home', 'index', 'category', 'tag', 'performer', 'channel', 'search'];
            if (in_array($template, $listingTemplates, true) && !empty($data['videos']) && is_array($data['videos'])) {
                $best = self::highestCtrVideo($data['videos']);
                if ($best !== '') {
                    return $siteUrl . $best;
                }
            }
        }

         
        $ogImage = Setting::get('seo_og_image', '');
        if ($ogImage !== '') {
            return $siteUrl . '/uploads/seo/' . $ogImage;
        }

        return '';
    }

    

    private static function highestCtrVideo(array $videos): string
    {
        $best = null;
        $bestScore = -1.0;

        foreach ($videos as $video) {
            $score = (float) ($video['ctr_score'] ?? 0);
            if ($score > $bestScore) {
                $bestScore = $score;
                $best = $video;
            }
        }

        if ($best === null) return '';

        if (!empty($best['video_folder'])) {
            return VideoAsset::url($best['video_folder'], 'thumb');
        }
        if (!empty($best['thumbnail'])) {
            return '/uploads/' . $best['thumbnail'];
        }

        return '';
    }

    

    private static function cleanTitle(string $title): string
    {
        return preg_replace('/\s{2,}/', ' ', trim($title));
    }

    

    public static function applyDescriptionPattern(string $pattern, array $vars): string
    {
        $result = strtr($pattern, $vars);
        $result = strip_tags($result);
        $result = preg_replace('/\s+/', ' ', trim($result));
        return mb_substr($result, 0, 160);
    }

    

    public static function shortText(string $text, int $maxLen): string
    {
        $clean = strip_tags($text);
        $clean = preg_replace('/\s+/', ' ', trim($clean));
        if (mb_strlen($clean) > $maxLen) {
            return mb_substr($clean, 0, $maxLen - 3) . '...';
        }
        return $clean;
    }

    

    public static function extractCount(array $data): string
    {
        if (isset($data['pagination']) && is_object($data['pagination']) && isset($data['pagination']->total)) {
            return number_format($data['pagination']->total);
        }
        if (isset($data['totalVideos'])) {
            return number_format((int) $data['totalVideos']);
        }
        return '0';
    }

    

    private static function extractNames($entities): string
    {
        if (!is_array($entities) || empty($entities)) {
            return '';
        }
        $names = [];
        foreach ($entities as $e) {
            $n = strip_tags((string) ($e['name'] ?? ''));
            if ($n !== '') {
                $names[] = $n;
            }
        }
        return implode(', ', $names);
    }

    

    /**
     * Does the active theme's SEO page already carry the site-visibility toggle?
     *
     * The toggle moved from Settings to SEO > Advanced in 1.0.70, but a core update never
     * replaces themes/ — so an install that updates the core while still running an older
     * theme would lose the control from Settings without gaining it on the SEO page, and a
     * staging site hidden from Google could not be un-hidden. Settings therefore keeps
     * rendering it until the theme in use provides it.
     */
    public static function themeOwnsSiteVisibility(): bool
    {
        static $cached = null;
        if ($cached !== null) {
            return $cached;
        }
        $path = ThemeManager::themePath() . '/admin/seo.php';
        $cached = is_file($path) && str_contains((string) @file_get_contents($path), 'seo_noindex_entire_site');
        return $cached;
    }

    public static function taxonomyMinItems(): int
    {
        return max(0, min(100, (int) Setting::get('seo_taxonomy_min_items', '0')));
    }

    public static function taxonomyKeepDescribed(): bool
    {
        return Setting::get('seo_taxonomy_keep_described', '1') === '1';
    }

    public static function saveTaxonomyIndexing(int $min, bool $keepDescribed): bool
    {
        $min = max(0, min(100, $min));
        $changed = $min !== self::taxonomyMinItems() || $keepDescribed !== self::taxonomyKeepDescribed();

        Setting::set('seo_taxonomy_min_items', (string) $min);
        Setting::set('seo_taxonomy_keep_described', $keepDescribed ? '1' : '0');

        if ($changed) {
            if (!class_exists('SitemapController')) {
                require_once APP_ROOT . '/src/Front/SitemapController.php';
            }
            SitemapController::markDirty();
        }

        return $changed;
    }

    public static function taxonomySitemapCondition(string $sitemapKey): string
    {
        $min = self::taxonomyMinItems();
        if ($min < 1) {
            return '';
        }

        foreach (self::TAXONOMY_INDEX_TYPES as $def) {
            if ($def['sitemap'] !== $sitemapKey) {
                continue;
            }
            $condition = $def['count'] . ' >= ' . $min;
            if ($def['desc'] !== '' && self::taxonomyKeepDescribed()) {
                $condition = '(' . $condition . ' OR CHAR_LENGTH(TRIM(' . $def['desc'] . ')) >= '
                    . self::TAXONOMY_DESCRIPTION_MIN_CHARS . ')';
            }
            return $condition;
        }

        return '';
    }

    public static function taxonomyIsThin(string $template, array $entity): bool
    {
        $min = self::taxonomyMinItems();
        if ($min < 1) {
            return false;
        }

        $def = self::TAXONOMY_INDEX_TYPES[$template] ?? null;
        if ($def === null) {
            return false;
        }

        if ((int) ($entity[$def['count']] ?? 0) >= $min) {
            return false;
        }

        if ($def['desc'] === '' || !self::taxonomyKeepDescribed()) {
            return true;
        }

        return !self::taxonomyHasOwnDescription($def, $entity);
    }

    private static function taxonomyHasOwnDescription(array $def, array $entity): bool
    {
        $column = $def['desc'];
        $value = (string) ($entity[$column] ?? '');

        if (Translator::locale() !== Translator::getDefaultCode()) {
            $id = (int) ($entity['id'] ?? 0);
            if ($id < 1) {
                return false;
            }
            $row = Database::fetchOne(
                'SELECT ' . $column . ' AS v FROM ' . $def['table'] . ' WHERE id = ?',
                [$id]
            );
            $value = (string) ($row['v'] ?? '');
        }

        return mb_strlen(trim($value, ' ')) >= self::TAXONOMY_DESCRIPTION_MIN_CHARS;
    }

    private static function detectThinTaxonomy(array $data, string $template): bool
    {
        $def = self::TAXONOMY_INDEX_TYPES[$template] ?? null;
        if ($def === null) {
            return false;
        }

        $entity = $data[$def['data']] ?? null;
        if (!is_array($entity)) {
            return false;
        }

        return self::taxonomyIsThin($template, $entity);
    }

    public static function taxonomyIndexPreview(int $min, bool $keepDescribed): array
    {
        $min = max(0, min(100, $min));
        $albumsOn = Album::featureEnabled();
        $rows = [];

        foreach (self::TAXONOMY_INDEX_TYPES as $template => $def) {
            if (!$albumsOn && str_starts_with($template, 'album')) {
                continue;
            }

            $total = (int) (Database::fetchOne('SELECT COUNT(*) AS c FROM ' . $def['table'])['c'] ?? 0);
            $hidden = 0;

            if ($min >= 1 && $total > 0) {
                $where = $def['count'] . ' < ' . $min;
                if ($def['desc'] !== '' && $keepDescribed) {
                    $where .= ' AND (' . $def['desc'] . ' IS NULL OR CHAR_LENGTH(TRIM(' . $def['desc'] . ')) < '
                        . self::TAXONOMY_DESCRIPTION_MIN_CHARS . ')';
                }
                $hidden = (int) (Database::fetchOne(
                    'SELECT COUNT(*) AS c FROM ' . $def['table'] . ' WHERE ' . $where
                )['c'] ?? 0);
            }

            $rows[] = [
                'type' => $template,
                'label' => $def['label'],
                'route' => $def['route'],
                'total' => $total,
                'hidden' => $hidden,
                'described' => $def['desc'] !== '',
                'type_hidden' => Setting::get($def['noindex'], '0') === '1',
            ];
        }

        return $rows;
    }

    public static function themeOwnsTaxonomyIndexing(): bool
    {
        static $cached = null;
        if ($cached !== null) {
            return $cached;
        }
        $path = ThemeManager::themePath() . '/admin/seo.php';
        $cached = is_file($path) && str_contains((string) @file_get_contents($path), 'seo_taxonomy_min_items');
        return $cached;
    }

    public static function renderHeadMeta(): void
    {
        $template = ThemeRenderer::currentTemplate();
        $page = max(1, (int) ($_GET['page'] ?? 1));

         
         
         
         
        $status = (int) (http_response_code() ?: 200);
        if ($template === '404' || $status >= 400) {
            $follow = Setting::get('seo_noindex_entire_site', '0') === '1' ? ', nofollow' : ', follow';
            echo '<meta name="robots" content="noindex' . $follow . '">' . "\n";
            self::renderVerificationAndScripts();
            return;
        }

         
        $noindex = false;
        if (Setting::get('seo_noindex_entire_site', '0') === '1') $noindex = true;
        if (Setting::get('seo_noindex_tags', '0') === '1' && $template === 'tag') $noindex = true;
        if (Setting::get('seo_noindex_categories', '0') === '1' && $template === 'category') $noindex = true;
        if (Setting::get('seo_noindex_performers', '0') === '1' && $template === 'performer') $noindex = true;
        if (Setting::get('seo_noindex_channels', '0') === '1' && $template === 'channel') $noindex = true;
        if (Setting::get('seo_noindex_albums', '0') === '1' && in_array($template, ['album', 'albums', 'album-category', 'album-tag', 'album-pornstar', 'album-channel'], true)) $noindex = true;
        if (Setting::get('seo_noindex_search', '1') === '1' && $template === 'search') $noindex = true;
        if (Setting::get('seo_noindex_paginated', '0') === '1' && $page > 1) $noindex = true;
        if (self::$thinTaxonomy) $noindex = true;

        if ($noindex) {
            $nofollow = Setting::get('seo_noindex_entire_site', '0') === '1' ? ', nofollow' : ', follow';
            echo '<meta name="robots" content="noindex' . $nofollow . '">' . "\n";
        } else {
            echo '<meta name="robots" content="max-video-preview:-1, max-image-preview:large, max-snippet:-1">' . "\n";
        }

        self::renderVerificationAndScripts();
    }

    

    private static function renderVerificationAndScripts(): void
    {
         
        $verifications = [
            'seo_google_verify'    => 'google-site-verification',
            'seo_bing_verify'      => 'msvalidate.01',
            'seo_pinterest_verify' => 'p:domain_verify',
            'seo_yandex_verify'    => 'yandex-verification',
        ];
        foreach ($verifications as $key => $metaName) {
            $val = Setting::get($key, '');
            if ($val !== '') {
                echo '<meta name="' . htmlspecialchars($metaName) . '" content="' . htmlspecialchars($val) . '">' . "\n";
            }
        }

         
        $ga4Id = Setting::get('seo_ga4_id', '');
        if ($ga4Id !== '' && preg_match('/^G-[A-Z0-9]+$/', $ga4Id)) {
            echo '<script async src="https://www.googletagmanager.com/gtag/js?id=' . htmlspecialchars($ga4Id) . '"></script>' . "\n";
            echo '<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag("js",new Date());gtag("config","' . htmlspecialchars($ga4Id) . '");</script>' . "\n";
        }

         
        $customHead = Setting::get('seo_custom_head', '');
        if ($customHead !== '') {
            echo $customHead . "\n";
        }
    }
}
