<?php

namespace WPSN\Modules\Robots\Admin;

use WPSN\Core\Capabilities;
use WPSN\Core\Container;
use WPSN\Core\Settings;
use WPSN\Modules\ActivityLog\ActivityLogRepository;

defined('ABSPATH') || exit;

/**
 * URL Rules Center (Noindex)
 *
 * Manages structured URL-pattern rules stored in settings under robots.url_noindex_rules.
 * These rules are applied via wp_robots (see MetaTags module) and allow one-click
 * "noindex" for noisy URL patterns (pagination params, tracking params, facets, etc.).
 */
final class UrlRulesAdmin
{
    public const PAGE_SLUG = 'wpsn-url-rules';

    private Container $container;
    private Settings $settings;

    public function __construct(Container $container)
    {
        $this->container = $container;
        $this->settings = $container->get('settings');
    }

    public function register(): void
    {
        add_action('admin_menu', [$this, 'addMenu']);

        add_action('admin_post_wpsn_url_rules_save', [$this, 'handleSave']);
        add_action('admin_post_wpsn_url_rules_add', [$this, 'handleAdd']);
        add_action('admin_post_wpsn_url_rules_apply_preset', [$this, 'handleApplyPreset']);
        add_action('admin_post_wpsn_url_rules_delete', [$this, 'handleDelete']);
    }

    public function addMenu(): void
    {
        add_management_page(
            __('URL Rules', 'wp-seo-next'),
            __('URL Rules', 'wp-seo-next'),
            Capabilities::CAP_TOOLS,
            self::PAGE_SLUG,
            [$this, 'render']
        );
    }

    private function pageUrl(array $args = []): string
    {
        $base = admin_url('tools.php?page=' . self::PAGE_SLUG);
        if (empty($args)) {
            return $base;
        }
        return add_query_arg($args, $base);
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    private function getRules(): array
    {
        $rules = $this->settings->get('robots.url_noindex_rules', []);
        return is_array($rules) ? $rules : [];
    }

    private function updateRules(array $rules): void
    {
        /** @var \WPSN\Admin\SettingsRepository $repo */
        $repo = $this->container->get('settings_repo');
        $all = $repo->getAll();
        if (!isset($all['robots']) || !is_array($all['robots'])) {
            $all['robots'] = [];
        }
        $all['robots']['url_noindex_rules'] = $rules;
        $repo->update($all);
    }

    private function log(string $action, string $message, array $meta = []): void
    {
        try {
            $repo = new ActivityLogRepository();
            $repo->insert($action, $message, [
                'context' => 'url_rules',
                'meta' => $meta,
            ]);
        } catch (\Throwable $e) {
            // Non-fatal.
        }
    }

    /**
     * @return array<int,string>
     */
    private function allowedMatchers(): array
    {
        return ['path_prefix', 'path_regex', 'query_param', 'query_param_prefix', 'query_param_int_gt1'];
    }

    private function matcherLabel(string $match): string
    {
        switch ($match) {
            case 'path_prefix':
                return __('Path prefix', 'wp-seo-next');
            case 'path_regex':
                return __('Path regex', 'wp-seo-next');
            case 'query_param':
                return __('Query param exists', 'wp-seo-next');
            case 'query_param_prefix':
                return __('Query param prefix', 'wp-seo-next');
            case 'query_param_int_gt1':
                return __('Query param int > 1', 'wp-seo-next');
            default:
                return $match;
        }
    }

    private function newRuleId(): string
    {
        // Short stable id.
        return 'r_' . substr(md5(uniqid('wpsn', true)), 0, 10);
    }

    /**
     * @param array<int,array<string,mixed>> $rules
     */
    private function hasDuplicate(array $rules, string $match, string $pattern, string $pathPrefix): bool
    {
        $match = sanitize_key($match);
        $pattern = trim((string) $pattern);
        $pathPrefix = trim((string) $pathPrefix);
        foreach ($rules as $r) {
            if (!is_array($r)) {
                continue;
            }
            $m = isset($r['match']) ? sanitize_key((string) $r['match']) : '';
            $p = isset($r['pattern']) ? trim((string) $r['pattern']) : '';
            $pp = isset($r['path_prefix']) ? trim((string) $r['path_prefix']) : '';
            if ($m === $match && $p === $pattern && $pp === $pathPrefix) {
                return true;
            }
        }
        return false;
    }

    public function handleSave(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_url_rules_save');

        $in = $_POST['rules'] ?? [];
        if (!is_array($in)) {
            $in = [];
        }

        $allowed = $this->allowedMatchers();

        $out = [];
        $count = 0;
        foreach ($in as $row) {
            if (!is_array($row)) {
                continue;
            }

            $id = isset($row['id']) ? sanitize_key((string) wp_unslash($row['id'])) : '';
            if ($id === '') {
                continue;
            }

            $match = isset($row['match']) ? sanitize_key((string) wp_unslash($row['match'])) : '';
            if (!in_array($match, $allowed, true)) {
                continue;
            }

            $pattern = isset($row['pattern']) ? trim((string) wp_unslash($row['pattern'])) : '';
            $pathPrefix = isset($row['path_prefix']) ? trim((string) wp_unslash($row['path_prefix'])) : '';
            $note = isset($row['note']) ? sanitize_text_field((string) wp_unslash($row['note'])) : '';
            $source = isset($row['source']) ? sanitize_key((string) wp_unslash($row['source'])) : '';
            $createdAt = isset($row['created_at']) ? sanitize_text_field((string) wp_unslash($row['created_at'])) : '';

            $pattern = sanitize_text_field($pattern);
            $pathPrefix = $pathPrefix !== '' ? sanitize_text_field($pathPrefix) : '';
            if ($pathPrefix !== '' && $pathPrefix[0] !== '/') {
                $pathPrefix = '/' . ltrim($pathPrefix, '/');
            }

            if ($pattern === '') {
                continue;
            }

            $enabled = !empty($row['enabled']) ? 1 : 0;

            $out[] = [
                'id' => $id,
                'enabled' => $enabled,
                'match' => $match,
                'pattern' => $pattern,
                'path_prefix' => $pathPrefix,
                'note' => $note,
                'source' => $source,
                'created_at' => $createdAt,
            ];

            $count++;
            if ($count >= 500) {
                break;
            }
        }

        $before = count($this->getRules());
        $after = count($out);

        $this->updateRules($out);

        $this->log('robots_url_rules_saved', 'URL rules saved.', [
            'before' => $before,
            'after' => $after,
        ]);

        wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'saved']));
        exit;
    }

    public function handleAdd(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_url_rules_add');

        $match = isset($_POST['match']) ? sanitize_key((string) wp_unslash($_POST['match'])) : '';
        $pattern = isset($_POST['pattern']) ? trim((string) wp_unslash($_POST['pattern'])) : '';
        $pathPrefix = isset($_POST['path_prefix']) ? trim((string) wp_unslash($_POST['path_prefix'])) : '';
        $note = isset($_POST['note']) ? sanitize_text_field((string) wp_unslash($_POST['note'])) : '';

        $pattern = sanitize_text_field($pattern);
        $pathPrefix = $pathPrefix !== '' ? sanitize_text_field($pathPrefix) : '';
        if ($pathPrefix !== '' && $pathPrefix[0] !== '/') {
            $pathPrefix = '/' . ltrim($pathPrefix, '/');
        }

        $allowed = $this->allowedMatchers();
        if (!in_array($match, $allowed, true)) {
            $match = 'query_param';
        }

        if ($pattern === '') {
            wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'empty_pattern']));
            exit;
        }

        $rules = $this->getRules();
        if ($this->hasDuplicate($rules, $match, $pattern, $pathPrefix)) {
            wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'duplicate']));
            exit;
        }

        $rules[] = [
            'id' => $this->newRuleId(),
            'enabled' => 1,
            'match' => $match,
            'pattern' => $pattern,
            'path_prefix' => $pathPrefix,
            'note' => $note,
            'source' => 'manual',
            'created_at' => gmdate('c'),
        ];

        $this->updateRules($rules);

        $this->log('robots_url_rule_added', 'URL rule added.', [
            'match' => $match,
            'pattern' => $pattern,
            'path_prefix' => $pathPrefix,
        ]);

        wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'rule_added']));
        exit;
    }

    public function handleApplyPreset(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_url_rules_apply_preset');

        $preset = isset($_POST['preset']) ? sanitize_key((string) wp_unslash($_POST['preset'])) : '';
        if ($preset === '') {
            wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'preset_invalid']));
            exit;
        }

        $defs = $this->presetRules($preset);
        if (empty($defs)) {
            wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'preset_invalid']));
            exit;
        }

        $rules = $this->getRules();
        $added = 0;

        foreach ($defs as $d) {
            $match = isset($d['match']) ? sanitize_key((string) $d['match']) : '';
            $pattern = isset($d['pattern']) ? sanitize_text_field((string) $d['pattern']) : '';
            $pathPrefix = isset($d['path_prefix']) ? sanitize_text_field((string) $d['path_prefix']) : '';
            $note = isset($d['note']) ? sanitize_text_field((string) $d['note']) : '';

            if ($pattern === '' || $match === '') {
                continue;
            }

            if ($pathPrefix !== '' && $pathPrefix[0] !== '/') {
                $pathPrefix = '/' . ltrim($pathPrefix, '/');
            }

            if ($this->hasDuplicate($rules, $match, $pattern, $pathPrefix)) {
                continue;
            }

            $rules[] = [
                'id' => $this->newRuleId(),
                'enabled' => 1,
                'match' => $match,
                'pattern' => $pattern,
                'path_prefix' => $pathPrefix,
                'note' => $note,
                'source' => 'preset',
                'created_at' => gmdate('c'),
            ];
            $added++;

            if (count($rules) >= 500) {
                break;
            }
        }

        $this->updateRules($rules);

        $this->log('robots_url_rules_preset_applied', 'URL rules preset applied.', [
            'preset' => $preset,
            'added' => $added,
        ]);

        wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'preset_applied', 'added' => $added]));
        exit;
    }

    public function handleDelete(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'wp-seo-next'));
        }
        check_admin_referer('wpsn_url_rules_delete');

        $id = isset($_GET['id']) ? sanitize_key((string) wp_unslash($_GET['id'])) : '';
        if ($id === '') {
            wp_safe_redirect($this->pageUrl(['wpsn_notice' => 'delete_failed']));
            exit;
        }

        $rules = $this->getRules();
        $out = [];
        $deleted = false;

        foreach ($rules as $r) {
            if (!is_array($r)) {
                continue;
            }
            $rid = isset($r['id']) ? sanitize_key((string) $r['id']) : '';
            if ($rid !== '' && $rid === $id) {
                $deleted = true;
                continue;
            }
            $out[] = $r;
        }

        $this->updateRules($out);

        if ($deleted) {
            $this->log('robots_url_rule_deleted', 'URL rule deleted.', ['id' => $id]);
        }

        wp_safe_redirect($this->pageUrl(['wpsn_notice' => $deleted ? 'deleted' : 'delete_failed']));
        exit;
    }

    /**
     * @return array<int,array{match:string,pattern:string,path_prefix?:string,note?:string}>
     */
    private function presetRules(string $preset): array
    {
        switch ($preset) {
            case 'pagination':
                return [
                    [
                        'match' => 'path_regex',
                        'pattern' => '/page/[0-9]+/?$',
                        'note' => __('Preset: pagination (/page/2/)', 'wp-seo-next'),
                    ],
                    [
                        'match' => 'query_param_int_gt1',
                        'pattern' => 'paged',
                        'note' => __('Preset: pagination (?paged=2)', 'wp-seo-next'),
                    ],
                ];
            case 'tracking':
                return [
                    [
                        'match' => 'query_param_prefix',
                        'pattern' => 'utm_',
                        'note' => __('Preset: tracking params (utm_*)', 'wp-seo-next'),
                    ],
                    [
                        'match' => 'query_param',
                        'pattern' => 'gclid',
                        'note' => __('Preset: tracking param (gclid)', 'wp-seo-next'),
                    ],
                    [
                        'match' => 'query_param',
                        'pattern' => 'fbclid',
                        'note' => __('Preset: tracking param (fbclid)', 'wp-seo-next'),
                    ],
                ];
            case 'facets':
                return [
                    [
                        'match' => 'query_param_prefix',
                        'pattern' => 'filter_',
                        'note' => __('Preset: faceted navigation (filter_*)', 'wp-seo-next'),
                    ],
                    [
                        'match' => 'query_param_prefix',
                        'pattern' => 'facet_',
                        'note' => __('Preset: faceted navigation (facet_*)', 'wp-seo-next'),
                    ],
                    [
                        'match' => 'query_param',
                        'pattern' => 'orderby',
                        'note' => __('Preset: sorting (orderby)', 'wp-seo-next'),
                    ],
                    [
                        'match' => 'query_param',
                        'pattern' => 'order',
                        'note' => __('Preset: sorting (order)', 'wp-seo-next'),
                    ],
                ];
            case 'search':
                return [
                    [
                        'match' => 'query_param',
                        'pattern' => 's',
                        'note' => __('Preset: WP search (?s=...)', 'wp-seo-next'),
                    ],
                ];
            default:
                return [];
        }
    }

    /**
     * @param array<int,array<string,mixed>> $rules
     * @return array{matched:bool,matched_ids:array<int,string>,path:string,query_keys:array<int,string>}
     */
    private function testUrlAgainstRules(string $url, array $rules): array
    {
        $u = trim($url);
        if ($u === '') {
            return [
                'matched' => false,
                'matched_ids' => [],
                'path' => '',
                'query_keys' => [],
            ];
        }

        // Accept full URL or just a path/query.
        if (strpos($u, '://') === false && $u[0] !== '/') {
            $u = '/' . $u;
        }
        if (strpos($u, '://') === false) {
            $u = home_url($u);
        }

        $path = (string) wp_parse_url($u, PHP_URL_PATH);
        $query = (string) wp_parse_url($u, PHP_URL_QUERY);

        if ($path === '') {
            $path = '/';
        }
        if ($path[0] !== '/') {
            $path = '/' . ltrim($path, '/');
        }

        $q = [];
        if ($query !== '') {
            parse_str($query, $q);
            if (!is_array($q)) {
                $q = [];
            }
        }

        $matchedIds = [];
        $matched = false;

        foreach ($rules as $r) {
            if (!is_array($r)) {
                continue;
            }
            if (empty($r['enabled'])) {
                continue;
            }

            $match = isset($r['match']) ? (string) $r['match'] : '';
            $pattern = isset($r['pattern']) ? (string) $r['pattern'] : '';
            $scope = isset($r['path_prefix']) ? (string) $r['path_prefix'] : '';
            $id = isset($r['id']) ? sanitize_key((string) $r['id']) : '';

            $pattern = trim($pattern);
            $scope = trim($scope);

            if ($match === '' || $pattern === '') {
                continue;
            }

            if ($scope !== '') {
                if ($scope[0] !== '/') {
                    $scope = '/' . ltrim($scope, '/');
                }
                if (strpos($path, $scope) !== 0) {
                    continue;
                }
            }

            $hit = false;

            if ($match === 'path_prefix') {
                if ($pattern[0] !== '/') {
                    $pattern = '/' . ltrim($pattern, '/');
                }
                $hit = (strpos($path, $pattern) === 0);
            } elseif ($match === 'path_regex') {
                $re = '#' . $pattern . '#';
                $hit = (@preg_match($re, $path) === 1);
            } elseif ($match === 'query_param') {
                $key = sanitize_key($pattern);
                $hit = ($key !== '' && array_key_exists($key, $q));
            } elseif ($match === 'query_param_int_gt1') {
                $key = sanitize_key($pattern);
                if ($key !== '' && array_key_exists($key, $q)) {
                    $val = $q[$key];
                    if (is_array($val)) {
                        $val = reset($val);
                    }
                    $val = is_scalar($val) ? (string) $val : '';
                    $val = trim($val);
                    $hit = ($val !== '' && is_numeric($val) && (int) $val > 1);
                }
            } elseif ($match === 'query_param_prefix') {
                $prefix = sanitize_key($pattern);
                if ($prefix !== '') {
                    foreach (array_keys($q) as $k) {
                        $k = sanitize_key((string) $k);
                        if ($k !== '' && strpos($k, $prefix) === 0) {
                            $hit = true;
                            break;
                        }
                    }
                }
            }

            if ($hit) {
                $matched = true;
                if ($id !== '') {
                    $matchedIds[] = $id;
                }
            }
        }

        $keys = [];
        foreach (array_keys($q) as $k) {
            $k = sanitize_key((string) $k);
            if ($k !== '') {
                $keys[] = $k;
            }
        }

        return [
            'matched' => $matched,
            'matched_ids' => array_values(array_unique($matchedIds)),
            'path' => $path,
            'query_keys' => array_values(array_unique($keys)),
        ];
    }

    public function render(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to access this page.', 'wp-seo-next'));
        }

        $rules = $this->getRules();
        $notice = isset($_GET['wpsn_notice']) ? sanitize_key((string) wp_unslash($_GET['wpsn_notice'])) : '';

        $testUrl = isset($_GET['test_url']) ? (string) wp_unslash($_GET['test_url']) : '';
        $testUrl = is_string($testUrl) ? trim($testUrl) : '';

        $test = null;
        if ($testUrl !== '') {
            $test = $this->testUrlAgainstRules($testUrl, $rules);
        }

        $active = 0;
        foreach ($rules as $r) {
            if (is_array($r) && !empty($r['enabled'])) {
                $active++;
            }
        }

        echo '<div class="wrap">';
        echo '<h1>' . esc_html__('URL Rules (Noindex)', 'wp-seo-next') . '</h1>';
        echo '<p class="description">' . esc_html__('These rules set robots=noindex for matching URL patterns (via wp_robots). Useful for pagination, tracking parameters, facets, and other crawl-noisy URLs.', 'wp-seo-next') . '</p>';

        if ($notice !== '') {
            $type = 'info';
            $msg = '';
            switch ($notice) {
                case 'saved':
                    $type = 'success';
                    $msg = __('URL rules saved.', 'wp-seo-next');
                    break;
                case 'rule_added':
                    $type = 'success';
                    $msg = __('Rule added.', 'wp-seo-next');
                    break;
                case 'preset_applied':
                    $type = 'success';
                    $added = isset($_GET['added']) ? max(0, (int) $_GET['added']) : 0;
                    $msg = $added > 0
                        ? sprintf(__('Preset applied. Added: %d rule(s).', 'wp-seo-next'), $added)
                        : __('Preset applied. No new rules were added (duplicates skipped).', 'wp-seo-next');
                    break;
                case 'deleted':
                    $type = 'success';
                    $msg = __('Rule deleted.', 'wp-seo-next');
                    break;
                case 'duplicate':
                    $type = 'warning';
                    $msg = __('That rule already exists.', 'wp-seo-next');
                    break;
                case 'empty_pattern':
                    $type = 'error';
                    $msg = __('Pattern cannot be empty.', 'wp-seo-next');
                    break;
                case 'delete_failed':
                    $type = 'error';
                    $msg = __('Could not delete the rule (missing id).', 'wp-seo-next');
                    break;
                case 'preset_invalid':
                    $type = 'error';
                    $msg = __('Invalid preset.', 'wp-seo-next');
                    break;
            }
            if ($msg !== '') {
                echo '<div class="notice notice-' . esc_attr($type) . '"><p>' . esc_html($msg) . '</p></div>';
            }
        }

        echo '<div class="notice notice-info"><p>' . esc_html(sprintf(__('Total rules: %1$d (active: %2$d).', 'wp-seo-next'), count($rules), $active)) . '</p></div>';

        // Test URL.
        echo '<h2>' . esc_html__('Test a URL', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . esc_html__('Enter a URL (or just /path?query=1) to see whether it matches any active rule.', 'wp-seo-next') . '</p>';

        echo '<form method="get" action="' . esc_url(admin_url('tools.php')) . '">';
        echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
        echo '<input type="text" name="test_url" style="min-width:420px;" value="' . esc_attr($testUrl) . '" placeholder="/category/page/2/ or ?paged=2" /> ';
        submit_button(__('Test', 'wp-seo-next'), 'secondary', 'submit', false);
        echo '</form>';

        if (is_array($test)) {
            $matched = !empty($test['matched']);
            echo '<div class="postbox" style="padding:12px;max-width:980px;">';
            echo '<h3 style="margin-top:0;">' . esc_html__('Result', 'wp-seo-next') . '</h3>';
            echo '<p>' . esc_html__('Path:', 'wp-seo-next') . ' <code>' . esc_html((string) ($test['path'] ?? '')) . '</code></p>';
            $keys = isset($test['query_keys']) && is_array($test['query_keys']) ? $test['query_keys'] : [];
            echo '<p>' . esc_html__('Query keys:', 'wp-seo-next') . ' <code>' . esc_html($keys ? implode(', ', $keys) : '-') . '</code></p>';
            echo '<p><strong>' . esc_html__('Noindex would be applied:', 'wp-seo-next') . '</strong> ' . esc_html($matched ? __('YES', 'wp-seo-next') : __('NO', 'wp-seo-next')) . '</p>';

            if ($matched) {
                $ids = isset($test['matched_ids']) && is_array($test['matched_ids']) ? $test['matched_ids'] : [];
                if (!empty($ids)) {
                    echo '<p>' . esc_html__('Matched rule IDs:', 'wp-seo-next') . ' <code>' . esc_html(implode(', ', $ids)) . '</code></p>';
                }
            }
            echo '</div>';
        }


        // Presets.
        echo '<hr />';
        echo '<h2>' . esc_html__('One-click presets', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . esc_html__('Presets add common noindex rules. Duplicates are skipped automatically.', 'wp-seo-next') . '</p>';

        echo '<div style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">';
        foreach ([
            'pagination' => __('Pagination', 'wp-seo-next'),
            'tracking' => __('Tracking params', 'wp-seo-next'),
            'facets' => __('Facets & sorting', 'wp-seo-next'),
            'search' => __('WP search', 'wp-seo-next'),
        ] as $pid => $label) {
            echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;">';
            echo '<input type="hidden" name="action" value="wpsn_url_rules_apply_preset" />';
            echo '<input type="hidden" name="preset" value="' . esc_attr($pid) . '" />';
            wp_nonce_field('wpsn_url_rules_apply_preset');
            submit_button(sprintf(__('Apply: %s', 'wp-seo-next'), $label), 'secondary', 'submit', false);
            echo '</form>';
        }
        echo '</div>';

        // Existing rules.
        echo '<hr />';
        echo '<h2>' . esc_html__('Rules', 'wp-seo-next') . '</h2>';

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        echo '<input type="hidden" name="action" value="wpsn_url_rules_save" />';
        wp_nonce_field('wpsn_url_rules_save');

        echo '<table class="widefat striped" style="max-width:1400px;">';
        echo '<thead><tr>';
        echo '<th style="width:70px;">' . esc_html__('Enabled', 'wp-seo-next') . '</th>';
        echo '<th style="width:170px;">' . esc_html__('Matcher', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Pattern', 'wp-seo-next') . '</th>';
        echo '<th style="width:180px;">' . esc_html__('Scope (path prefix)', 'wp-seo-next') . '</th>';
        echo '<th style="width:220px;">' . esc_html__('Note', 'wp-seo-next') . '</th>';
        echo '<th style="width:120px;">' . esc_html__('Source', 'wp-seo-next') . '</th>';
        echo '<th style="width:170px;">' . esc_html__('Created', 'wp-seo-next') . '</th>';
        echo '<th style="width:90px;">' . esc_html__('Action', 'wp-seo-next') . '</th>';
        echo '</tr></thead>';
        echo '<tbody>';

        if (empty($rules)) {
            echo '<tr><td colspan="8">' . esc_html__('No rules yet. Add one below or apply a preset.', 'wp-seo-next') . '</td></tr>';
        } else {
            $allowed = $this->allowedMatchers();
            $i = 0;
            foreach ($rules as $r) {
                if (!is_array($r)) {
                    continue;
                }
                $id = isset($r['id']) ? sanitize_key((string) $r['id']) : '';
                if ($id === '') {
                    continue;
                }
                $enabled = !empty($r['enabled']);
                $match = isset($r['match']) ? sanitize_key((string) $r['match']) : '';
                $pattern = isset($r['pattern']) ? (string) $r['pattern'] : '';
                $pathPrefix = isset($r['path_prefix']) ? (string) $r['path_prefix'] : '';
                $note = isset($r['note']) ? (string) $r['note'] : '';
                $source = isset($r['source']) ? sanitize_key((string) $r['source']) : '';
                $createdAt = isset($r['created_at']) ? (string) $r['created_at'] : '';

                echo '<tr>';
                echo '<td>';
                echo '<input type="hidden" name="rules[' . (int) $i . '][id]" value="' . esc_attr($id) . '" />';
                echo '<input type="hidden" name="rules[' . (int) $i . '][source]" value="' . esc_attr($source) . '" />';
                echo '<input type="hidden" name="rules[' . (int) $i . '][created_at]" value="' . esc_attr($createdAt) . '" />';
                echo '<label><input type="checkbox" name="rules[' . (int) $i . '][enabled]" value="1" ' . checked($enabled, true, false) . ' /> ' . esc_html__('On', 'wp-seo-next') . '</label>';
                echo '</td>';

                echo '<td>';
                echo '<select name="rules[' . (int) $i . '][match]">';
                foreach ($allowed as $m) {
                    echo '<option value="' . esc_attr($m) . '" ' . selected($match, $m, false) . '>' . esc_html($this->matcherLabel($m)) . '</option>';
                }
                echo '</select>';
                echo '</td>';

                echo '<td><input type="text" name="rules[' . (int) $i . '][pattern]" value="' . esc_attr($pattern) . '" style="width:100%; min-width:260px;" /></td>';
                echo '<td><input type="text" name="rules[' . (int) $i . '][path_prefix]" value="' . esc_attr($pathPrefix) . '" placeholder="/shop" style="width:100%;" /></td>';
                echo '<td><input type="text" name="rules[' . (int) $i . '][note]" value="' . esc_attr($note) . '" style="width:100%;" /></td>';
                echo '<td><code>' . esc_html($source !== '' ? $source : '-') . '</code></td>';
                echo '<td><code>' . esc_html($createdAt !== '' ? $createdAt : '-') . '</code></td>';

                $delUrl = add_query_arg([
                    'action' => 'wpsn_url_rules_delete',
                    'id' => $id,
                ], admin_url('admin-post.php'));
                $delUrl = wp_nonce_url($delUrl, 'wpsn_url_rules_delete');
                echo '<td><a class="button-link delete" href="' . esc_url($delUrl) . '" onclick="return confirm(\'' . esc_js(__('Delete this rule?', 'wp-seo-next')) . '\');">' . esc_html__('Delete', 'wp-seo-next') . '</a></td>';
                echo '</tr>';

                $i++;
            }
        }

        echo '</tbody>';
        echo '</table>';

        echo '<p style="margin-top:10px;">';
        submit_button(__('Save changes', 'wp-seo-next'), 'primary', 'submit', false);
        echo '</p>';
        echo '</form>';


        // Add rule.
        echo '<hr />';
        echo '<h2>' . esc_html__('Add a new rule', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . esc_html__('Example: match “Query param int > 1” with pattern “paged” to noindex only ?paged=2+. Use Scope to limit to a section like /shop.', 'wp-seo-next') . '</p>';

        echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        echo '<input type="hidden" name="action" value="wpsn_url_rules_add" />';
        wp_nonce_field('wpsn_url_rules_add');

        echo '<select name="match">';
        foreach ($this->allowedMatchers() as $m) {
            echo '<option value="' . esc_attr($m) . '">' . esc_html($this->matcherLabel($m)) . '</option>';
        }
        echo '</select> ';
        echo '<input type="text" name="pattern" placeholder="paged" style="min-width:260px;" required /> ';
        echo '<input type="text" name="path_prefix" placeholder="/shop (optional)" style="min-width:200px;" /> ';
        echo '<input type="text" name="note" placeholder="note (optional)" style="min-width:220px;" /> ';
        submit_button(__('Add rule', 'wp-seo-next'), 'secondary', 'submit', false);
        echo '</form>';

        echo '</div>';
    }
}
