<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Commerce;

use Numinix\SeekmodoWordPress\Config\RemoteConfig;

/**
 * Phase 2 — server-side 302 for keyword merchandising redirects on
 * WooCommerce product search (Enter-submit without JavaScript).
 */
final class SearchRedirect
{
    public static function register(): void
    {
        if (!function_exists('add_action')) {
            return;
        }
        add_action('template_redirect', [self::class, 'maybeRedirect'], 5);
    }

    public static function maybeRedirect(): void
    {
        if (!function_exists('is_search') || !is_search()) {
            return;
        }
        if (isset($_GET['seekmodo_skip_category_redirect'])
            && (string) $_GET['seekmodo_skip_category_redirect'] === '1') {
            return;
        }
        $q = self::queryString();
        if ($q === '') {
            return;
        }
        $url = self::resolveRedirect($q);
        if ($url === null || $url === '') {
            return;
        }
        wp_safe_redirect($url, 302);
        exit;
    }

    private static function queryString(): string
    {
        if (function_exists('get_search_query')) {
            $q = trim((string) get_search_query(false));
            if ($q !== '') {
                return $q;
            }
        }
        return isset($_GET['s']) ? trim((string) wp_unslash($_GET['s'])) : '';
    }

    private static function resolveRedirect(string $q): ?string
    {
        $snap = RemoteConfig::cached();
        if (!is_array($snap)) {
            return null;
        }
        $rules = $snap['redirect_rules'] ?? null;
        if (!is_array($rules) || $rules === []) {
            return null;
        }
        $match = self::matchRules($rules, $q);
        if ($match === null) {
            return null;
        }
        return (string) ($match['target_url'] ?? '');
    }

    /**
     * @param list<array<string,mixed>> $rules
     * @return array<string,mixed>|null
     */
    private static function matchRules(array $rules, string $q): ?array
    {
        $qNorm = self::normalize($q);
        if ($qNorm === '') {
            return null;
        }
        $best = null;
        $bestPrio = -1;
        foreach ($rules as $row) {
            if (!is_array($row)) {
                continue;
            }
            $target = trim((string) ($row['target_url'] ?? ''));
            if ($target === '') {
                continue;
            }
            $matchMode = (string) ($row['match_mode'] ?? 'exact');
            $terms = $row['terms'] ?? [];
            if (!is_array($terms)) {
                $terms = [];
            }
            foreach ($terms as $term) {
                $term = trim((string) $term);
                if ($term === '') {
                    continue;
                }
                if (!self::termMatches($qNorm, $term, $matchMode)) {
                    continue;
                }
                $prio = (int) ($row['priority'] ?? 100);
                if ($best === null || $prio > $bestPrio) {
                    $bestPrio = $prio;
                    $best = ['target_url' => $target, 'matched_term' => $term];
                }
                break;
            }
        }
        return $best;
    }

    private static function termMatches(string $qNorm, string $term, string $matchMode): bool
    {
        $termNorm = self::normalize($term);
        if ($termNorm === '') {
            return false;
        }
        if ($matchMode === 'contains') {
            return stripos($qNorm, $termNorm) !== false
                || stripos($termNorm, $qNorm) !== false;
        }
        return $qNorm === $termNorm;
    }

    private static function normalize(string $s): string
    {
        $s = mb_strtolower(trim($s), 'UTF-8');
        $s = preg_replace('/\s+/', ' ', $s);

        return trim((string) $s);
    }
}
