<?php

namespace WPSN\Modules\Robots;

defined('ABSPATH') || exit;

/**
 * Structured robots.txt User-agent blocks managed from the Robots screen.
 *
 * This keeps common crawler-specific rules out of the free-form custom textarea
 * and makes one-click presets possible without fighting raw robots syntax.
 */
final class RobotsUserAgentRules
{
    /**
     * @return int[]
     */
    public static function crawlDelayChoices(): array
    {
        return [1, 2, 5];
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    public static function catalog(): array
    {
        return [
            'ccbot' => [
                'label' => 'CCBot',
                'group' => __('Archive / dataset crawlers', 'wp-seo-next'),
                'user_agent' => 'CCBot',
                'purpose' => __('Common Crawl dataset crawler.', 'wp-seo-next'),
                'lines' => ['Disallow: /'],
            ],
            'adsbot_google' => [
                'label' => 'AdsBot-Google',
                'group' => __('Ads / landing page checks', 'wp-seo-next'),
                'user_agent' => 'AdsBot-Google',
                'purpose' => __('Google Ads landing-page checks for desktop traffic.', 'wp-seo-next'),
                'lines' => ['Disallow: /'],
            ],
            'adsbot_google_mobile' => [
                'label' => 'AdsBot-Google-Mobile',
                'group' => __('Ads / landing page checks', 'wp-seo-next'),
                'user_agent' => 'AdsBot-Google-Mobile',
                'purpose' => __('Google Ads landing-page checks for mobile traffic.', 'wp-seo-next'),
                'lines' => ['Disallow: /'],
            ],
            'ahrefsbot' => [
                'label' => 'AhrefsBot',
                'group' => __('SEO crawlers', 'wp-seo-next'),
                'user_agent' => 'AhrefsBot',
                'purpose' => __('Third-party SEO crawler used for backlink and site analysis.', 'wp-seo-next'),
                'lines' => ['Disallow: /'],
            ],
            'semrushbot' => [
                'label' => 'SemrushBot',
                'group' => __('SEO crawlers', 'wp-seo-next'),
                'user_agent' => 'SemrushBot',
                'purpose' => __('Third-party SEO crawler used for site audits and rank tooling.', 'wp-seo-next'),
                'lines' => ['Disallow: /'],
            ],
            'perplexitybot' => [
                'label' => 'PerplexityBot',
                'group' => __('AI answer engines', 'wp-seo-next'),
                'user_agent' => 'PerplexityBot',
                'purpose' => __('Crawler used by Perplexity experiences.', 'wp-seo-next'),
                'lines' => ['Disallow: /'],
            ],
        ];
    }

    public static function normalizeUserAgent(string $userAgent): string
    {
        $userAgent = sanitize_text_field($userAgent);
        $userAgent = preg_replace('/[^A-Za-z0-9\-\._\*\/]+/', '', $userAgent) ?? '';
        $userAgent = trim($userAgent);

        if ($userAgent === '') {
            return '';
        }

        if (function_exists('mb_substr')) {
            $userAgent = (string) mb_substr($userAgent, 0, 120, 'UTF-8');
        } else {
            $userAgent = substr($userAgent, 0, 120);
        }

        return trim($userAgent);
    }

    public static function normalizeDirectiveLine(string $line): string
    {
        $line = trim((string) $line);
        $line = (string) preg_replace('/\s+/', ' ', $line);
        $line = trim($line);

        if ($line === '') {
            return '';
        }

        if (!preg_match('/^([A-Za-z\-]+)\s*:\s*(.*)$/', $line, $m)) {
            return '';
        }

        $directive = strtolower((string) $m[1]);
        $value = trim((string) $m[2]);

        if ($directive === 'disallow' || $directive === 'allow') {
            if ($value === '') {
                return '';
            }
            $value = sanitize_text_field($value);
            return ucfirst($directive) . ': ' . $value;
        }

        if ($directive === 'crawl-delay') {
            if ($value === '' || !is_numeric($value)) {
                return '';
            }
            return 'Crawl-delay: ' . (string) max(0, (int) $value);
        }

        return '';
    }

    public static function isGoogleFamilyUserAgent(string $userAgent): bool
    {
        $userAgent = strtolower(self::normalizeUserAgent($userAgent));
        if ($userAgent === '') {
            return false;
        }

        return strpos($userAgent, 'google') !== false || strpos($userAgent, 'adsbot') !== false;
    }

    public static function recommendedCrawlDelay(string $userAgent): ?int
    {
        $userAgent = strtolower(self::normalizeUserAgent($userAgent));
        if ($userAgent === '') {
            return null;
        }

        if (in_array($userAgent, ['ccbot', 'ahrefsbot', 'semrushbot'], true)) {
            return 2;
        }

        return null;
    }

    /**
     * @return 'ignored'|'supported'|'unclear'|'generic'
     */
    public static function crawlDelayBehavior(string $userAgent): string
    {
        $normalized = self::normalizeUserAgent($userAgent);
        if ($normalized === '') {
            return 'generic';
        }

        if (self::isGoogleFamilyUserAgent($normalized)) {
            return 'ignored';
        }

        if (self::recommendedCrawlDelay($normalized) !== null) {
            return 'supported';
        }

        if (strcasecmp($normalized, 'PerplexityBot') === 0) {
            return 'unclear';
        }

        return 'generic';
    }

    /**
     * @param array<int,mixed> $lines
     */
    public static function hasCrawlDelay(array $lines): bool
    {
        foreach (self::normalizeLines($lines) as $line) {
            if (stripos($line, 'Crawl-delay:') === 0) {
                return true;
            }
        }

        return false;
    }

    public static function crawlDelaySummary(string $userAgent): string
    {
        $behavior = self::crawlDelayBehavior($userAgent);

        if ($behavior === 'ignored') {
            return __('Google crawlers ignore Crawl-delay.', 'wp-seo-next');
        }

        $recommended = self::recommendedCrawlDelay($userAgent);
        if ($recommended !== null) {
            return sprintf(
                /* translators: %d is a suggested Crawl-delay value in seconds. */
                __('Suggested Crawl-delay: %d seconds.', 'wp-seo-next'),
                $recommended
            );
        }

        if ($behavior === 'unclear') {
            return __('Crawl-delay support is unclear; prefer Allow / Disallow.', 'wp-seo-next');
        }

        return __('Leave Crawl-delay empty by default.', 'wp-seo-next');
    }

    public static function crawlDelayHelpText(string $userAgent): string
    {
        $behavior = self::crawlDelayBehavior($userAgent);

        if ($behavior === 'ignored') {
            return __('Google crawlers ignore Crawl-delay. Leave it empty and use this block only for Allow / Disallow rules.', 'wp-seo-next');
        }

        $recommended = self::recommendedCrawlDelay($userAgent);
        if ($recommended !== null) {
            return sprintf(
                /* translators: %d is a suggested Crawl-delay value in seconds. */
                __('Suggested starting point: %d seconds. Leave Crawl-delay empty unless you intentionally want to slow this crawler down.', 'wp-seo-next'),
                $recommended
            );
        }

        if ($behavior === 'unclear') {
            return __('Crawl-delay support is not clearly documented for this crawler. Prefer Allow / Disallow unless you have a tested reason to add a delay.', 'wp-seo-next');
        }

        return __('Recommended shortcuts: 1, 2, or 5 seconds. Leave Crawl-delay empty by default and add it only for non-Google crawlers that need rate limiting.', 'wp-seo-next');
    }

    /**
     * @param array<int,mixed> $lines
     * @return string[]
     */
    public static function normalizeLines(array $lines): array
    {
        $out = [];
        $seen = [];

        foreach ($lines as $line) {
            if (!is_scalar($line)) {
                continue;
            }
            $normalized = self::normalizeDirectiveLine((string) $line);
            if ($normalized === '') {
                continue;
            }
            $key = strtolower($normalized);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $out[] = $normalized;

            if (count($out) >= 20) {
                break;
            }
        }

        return $out;
    }

    /**
     * @return string[]
     */
    public static function normalizeLinesFromText(string $text): array
    {
        $rows = preg_split('/\r\n|\r|\n/', $text) ?: [];
        return self::normalizeLines($rows);
    }

    /**
     * @param array<string,mixed> $all
     * @return array<int,array<string,mixed>>
     */
    public static function get(array $all): array
    {
        $rt = isset($all['robots_txt']) && is_array($all['robots_txt']) ? $all['robots_txt'] : [];
        $groups = isset($rt['agent_groups']) && is_array($rt['agent_groups']) ? $rt['agent_groups'] : [];
        return array_values(array_filter($groups, static fn ($group) => is_array($group)));
    }

    /**
     * @param array<string,mixed> $all
     */
    public static function addGroup(array &$all, string $userAgent, array $lines, string $source = '', string $note = ''): bool
    {
        $userAgent = self::normalizeUserAgent($userAgent);
        $lines = self::normalizeLines($lines);
        if ($userAgent === '' || empty($lines)) {
            return false;
        }

        if (!isset($all['robots_txt']) || !is_array($all['robots_txt'])) {
            $all['robots_txt'] = [];
        }
        if (!isset($all['robots_txt']['agent_groups']) || !is_array($all['robots_txt']['agent_groups'])) {
            $all['robots_txt']['agent_groups'] = [];
        }

        $groups = $all['robots_txt']['agent_groups'];
        $groups = is_array($groups) ? array_values(array_filter($groups, static fn ($group) => is_array($group))) : [];

        $existing = [];
        foreach ($groups as $group) {
            $ua = isset($group['user_agent']) ? self::normalizeUserAgent((string) $group['user_agent']) : '';
            $groupLines = isset($group['lines']) && is_array($group['lines']) ? self::normalizeLines($group['lines']) : [];
            if ($ua === '' || empty($groupLines)) {
                continue;
            }
            $existing[strtolower($ua . "\n" . implode("\n", $groupLines))] = true;
        }

        $fingerprint = strtolower($userAgent . "\n" . implode("\n", $lines));
        if (isset($existing[$fingerprint])) {
            return false;
        }

        $groups[] = [
            'id' => function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : (string) uniqid('wpsn_ua_', true),
            'enabled' => true,
            'user_agent' => $userAgent,
            'lines' => $lines,
            'source' => sanitize_key($source),
            'note' => sanitize_text_field($note),
            'created_at' => function_exists('current_time') ? (string) current_time('mysql', true) : gmdate('Y-m-d H:i:s'),
        ];

        if (count($groups) > 100) {
            $groups = array_slice($groups, 0, 100);
        }

        $all['robots_txt']['agent_groups'] = $groups;
        return true;
    }

    /**
     * @param array<string,mixed> $all
     */
    public static function addPreset(array &$all, string $presetKey): bool
    {
        $presetKey = sanitize_key($presetKey);
        $catalog = self::catalog();
        if (!isset($catalog[$presetKey]) || !is_array($catalog[$presetKey])) {
            return false;
        }

        $preset = $catalog[$presetKey];
        $userAgent = isset($preset['user_agent']) ? (string) $preset['user_agent'] : '';
        $lines = isset($preset['lines']) && is_array($preset['lines']) ? $preset['lines'] : [];
        $note = isset($preset['purpose']) ? (string) $preset['purpose'] : '';

        return self::addGroup($all, $userAgent, $lines, 'preset', $note);
    }

    /**
     * @param array<string,mixed> $all
     */
    public static function toggle(array &$all, string $id, bool $enabled): bool
    {
        $id = sanitize_text_field($id);
        if ($id === '' || !isset($all['robots_txt']['agent_groups']) || !is_array($all['robots_txt']['agent_groups'])) {
            return false;
        }

        $changed = false;
        foreach ($all['robots_txt']['agent_groups'] as &$group) {
            if (!is_array($group)) {
                continue;
            }
            if ((string) ($group['id'] ?? '') === $id) {
                $group['enabled'] = $enabled;
                $changed = true;
                break;
            }
        }
        unset($group);

        return $changed;
    }

    /**
     * @param array<string,mixed> $all
     */
    public static function delete(array &$all, string $id): bool
    {
        $id = sanitize_text_field($id);
        if ($id === '' || !isset($all['robots_txt']['agent_groups']) || !is_array($all['robots_txt']['agent_groups'])) {
            return false;
        }

        $before = count($all['robots_txt']['agent_groups']);
        $all['robots_txt']['agent_groups'] = array_values(array_filter(
            $all['robots_txt']['agent_groups'],
            static fn ($group) => !(is_array($group) && (string) ($group['id'] ?? '') === $id)
        ));

        return count($all['robots_txt']['agent_groups']) !== $before;
    }
}
