<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Frontend;

use Numinix\SeekmodoWordPress\Bootstrap;
use Numinix\SeekmodoWordPress\Config\Options;
use Numinix\SeekmodoWordPress\Gateway\BrowserToken;
use Numinix\SeekmodoWordPress\Rest\Routes;

/**
 * Storefront-side enqueue for the Seekmodo typeahead dropdown UI.
 *
 * Surfaces:
 *
 *   - `assets/typeahead/seekmodo-typeahead.js` (vanilla-JS dropdown
 *     that auto-binds to common search inputs and renders rows from
 *     the `/wp-json/seekmodo/v1/typeahead` endpoint).
 *   - `assets/typeahead/seekmodo-typeahead.css` (scoped styling with
 *     CSS-variable theme hooks so a child theme can re-skin the
 *     dropdown without forking).
 *   - `window.SEEKMODO_TYPEAHEAD` localized config object (endpoint,
 *     vertical, branding flag, i18n strings, override-able selectors).
 *
 * The enqueue is gated on a per-site setting that defaults to OFF.
 * The reasoning:
 *
 *   - Trek.ca already ships a bespoke typeahead in the theme that
 *     hits the same REST endpoint, complete with a "Powered by
 *     Seekmodo" badge driven by the `seekmodo_show_branding` filter.
 *     Auto-attaching this connector-side UI on top would render two
 *     overlapping dropdowns under the same input. Trek's site keeps
 *     the toggle OFF; the theme integration stays the source of truth.
 *
 *   - POCO.com has no theme-level typeahead. Flipping the toggle ON
 *     gives them an instant Seekmodo-powered search-suggestions box
 *     using the existing connector + REST endpoint. POCO is set to
 *     `brand=false` so the dropdown is white-labelled — the
 *     "Powered by Seekmodo" footer stays hidden, matching the
 *     gateway-default for non-partner tenants.
 *
 *   - Other tenants enable per-site as the connector roll-out widens.
 *
 * Filters / extension points:
 *
 *   - `seekmodo_enqueue_typeahead` (bool $enabled): preempts the
 *     option lookup. Themes can `__return_false` to suppress the UI
 *     site-wide regardless of the admin toggle.
 *
 *   - `seekmodo_show_branding` (bool $show): existing connector-wide
 *     filter; the typeahead localised config respects it the same way
 *     trek's mu-plugin does. Returning true lights the "Powered by"
 *     footer; returning false keeps the dropdown white-labelled.
 *
 *   - `seekmodo_typeahead_selectors` (string[] $selectors): override
 *     the CSS selectors the JS auto-attaches to. Default is
 *     `['input[type="search"]', 'input[name="s"]',
 *       'input[data-seekmodo-typeahead]']`. Pass a more specific list
 *     to keep the dropdown off non-search-bar inputs (e.g. the WC
 *     coupon-code field on the cart page that happens to be
 *     `type="search"`).
 *
 *   - `seekmodo_typeahead_config` (array $config): last-resort filter
 *     allowing a site to mutate any localized field before it's
 *     emitted. Use the explicit filters above where they fit.
 */
final class TypeaheadUI
{
    public const SCRIPT_HANDLE = 'seekmodo-typeahead';
    public const STYLE_HANDLE  = 'seekmodo-typeahead';

    public static function register(): void
    {
        if (!function_exists('add_action')) {
            return;
        }
        add_action('wp_enqueue_scripts', [self::class, 'enqueue']);
    }

    /**
     * Hook payload for `wp_enqueue_scripts`. Bails out cleanly on
     * admin / RSS / login / xmlrpc requests so the dropdown never
     * leaks into back-office contexts.
     */
    public static function enqueue(): void
    {
        if (!function_exists('wp_enqueue_script') || !function_exists('wp_register_script')) {
            return;
        }
        if (function_exists('is_admin') && is_admin()) {
            return;
        }
        // Honor the master-on/off switch first so a hot path on a
        // disabled site bails before we touch URL builders or option
        // reads.
        $enabled = self::isEnabled();
        $enabled = (bool) apply_filters('seekmodo_enqueue_typeahead', $enabled);
        if (!$enabled) {
            return;
        }

        $endpoint = function_exists('rest_url')
            ? (string) rest_url(Routes::NAMESPACE_NAME . '/typeahead')
            : '';
        if ($endpoint === '') {
            return;
        }

        $vertical = self::pickVertical();
        $brand    = self::shouldBrand();

        // SM-602 v0.5.0 — WP REST bypass. When a browser token is
        // available, the JS POSTs `mcp.seekmodo.com/v1/typeahead`
        // directly with the token + tenant id headers, skipping the
        // ~1.5 s WordPress boot per keystroke that the WP REST hop
        // pays on a heavy install. Falls back to the WP REST endpoint
        // automatically when:
        //   - the tenant isn't paired yet,
        //   - the gateway is unreachable / breaker open at mint time,
        //   - the cached token has expired between page render and
        //     the keystroke (the JS retries via the REST endpoint
        //     on a 401 from the direct path).
        // Filterable so a site can force the slower-but-always-correct
        // REST hop (e.g. during incident-response or to debug a
        // gateway-side issue without a connector update).
        $direct = (bool) apply_filters(
            'seekmodo_typeahead_gateway_direct',
            true
        );
        $bt = $direct ? BrowserToken::current() : null;

        $config = [
            'endpoint'    => $endpoint,
            'vertical'    => $vertical,
            'brand'       => $brand,
            'min_chars'   => 2,
            'max_rows'    => 8,
            'debounce_ms' => 150,
            // Per-keystroke client-side LRU cache size (added v0.5.0).
            // Backspace + retype on an already-fetched prefix renders
            // from in-memory cache, no network round-trip. Sized to
            // cover a typical user typing 3-5 unique prefixes per
            // session without growing the resident DOM payload.
            'cache_size'  => 32,
            'selectors'   => self::selectors(),
            'i18n'        => self::i18n(),
        ];
        if (is_array($bt)) {
            $config['gateway'] = [
                'url'        => (string) $bt['gateway_url'],
                'tenant'     => (string) $bt['tenant'],
                'token'      => (string) $bt['token'],
                'expires_at' => (int) $bt['expires_at'],
                'session_id' => (string) ($bt['session_id'] ?? ''),
                // Connector platform header so the gateway logs
                // can split per-platform fetch volume the same
                // way the HMAC-authed path does.
                'platform'   => 'wordpress',
                'version'    => defined('SEEKMODO_WP_VERSION') ? (string) SEEKMODO_WP_VERSION : '',
            ];
        }
        $config = apply_filters('seekmodo_typeahead_config', $config);

        $version = defined('SEEKMODO_WP_VERSION') ? (string) SEEKMODO_WP_VERSION : false;
        $base    = defined('SEEKMODO_WP_PLUGIN_URL') ? (string) SEEKMODO_WP_PLUGIN_URL : '';

        wp_register_style(
            self::STYLE_HANDLE,
            $base . 'assets/typeahead/seekmodo-typeahead.css',
            [],
            $version
        );
        wp_register_script(
            'seekmodo-cors-notice',
            $base . 'assets/suggest/seekmodo-cors-notice.js',
            [],
            $version,
            true
        );
        wp_register_script(
            self::SCRIPT_HANDLE,
            $base . 'assets/typeahead/seekmodo-typeahead.js',
            ['seekmodo-cors-notice'],
            $version,
            true
        );
        wp_enqueue_style(self::STYLE_HANDLE);
        wp_enqueue_script('seekmodo-cors-notice');
        wp_enqueue_script(self::SCRIPT_HANDLE);

        if (function_exists('wp_localize_script')) {
            wp_localize_script(self::SCRIPT_HANDLE, 'SEEKMODO_TYPEAHEAD', $config);
        }
    }

    /**
     * Master per-site toggle — `true` only when the operator has
     * explicitly enabled the connector typeahead UI in
     * wp-admin → Settings → Seekmodo. Defaults `false` so a fresh
     * v0.4.0 install on a site that already has a theme-level
     * typeahead doesn't double-render until the operator opts in.
     *
     * v0.6.0 — additionally gated on the `seekmodo_wp_suggest_legacy`
     * opt-out: when the master toggle is on but the legacy flag is
     * off, the new `<seekmodo-suggest>` web component
     * ({@see SuggestUI}) takes over and this legacy class returns
     * false to prevent double-attaching dropdowns to the same input.
     */
    public static function isEnabled(): bool
    {
        $master = get_option(Options::OPT_TYPEAHEAD_UI_ENABLED, '0');
        $masterOn = is_bool($master)
            ? $master
            : in_array(strtolower((string) $master), ['1', 'true', 'yes', 'on'], true);
        if (!$masterOn) {
            return false;
        }
        // v0.6.0 — yield to the universal suggest widget unless the
        // operator has explicitly opted into the legacy dropdown for
        // this site (e.g. while restyling their child theme to match
        // the new web-component markup).
        $legacy = get_option(Options::OPT_SUGGEST_LEGACY, '0');
        $legacyOn = is_bool($legacy)
            ? $legacy
            : in_array(strtolower((string) $legacy), ['1', 'true', 'yes', 'on'], true);

        return $legacyOn;
    }

    /**
     * Pick the storefront's primary vertical. Mirrors the same probe
     * Bootstrap uses to decide whether to register Commerce*
     * sub-modules — if both content and commerce are gateway-enabled
     * AND WooCommerce is active on this site, the typeahead targets
     * `commerce` (product cards) by default. Themes that want the
     * other vertical can pass `?vertical=content` or override via
     * the `seekmodo_typeahead_config` filter.
     */
    private static function pickVertical(): string
    {
        $verticals = Options::enabledVerticals();
        $woo       = Bootstrap::wooCommerceActive();
        if ($woo && isset($verticals['commerce'])) {
            return 'commerce';
        }

        return 'content';
    }

    /**
     * Whether to render the "Powered by Seekmodo" footer in the
     * dropdown. Source-of-truth is the gateway-pulled
     * {@see Options::OPT_SHOW_BRANDING} option, with the existing
     * `seekmodo_show_branding` filter layered on top. Returns the
     * same bool that `numinix_seekmodo_show_branding()` returns —
     * keeps the typeahead UI consistent with whatever the theme's
     * own integrations decided.
     */
    private static function shouldBrand(): bool
    {
        if (function_exists('numinix_seekmodo_show_branding')) {
            return (bool) numinix_seekmodo_show_branding();
        }
        $stored = Options::showBranding();

        return (bool) apply_filters('seekmodo_show_branding', $stored);
    }

    /**
     * @return array<int,string>
     */
    private static function selectors(): array
    {
        $defaults = [
            'input[type="search"]',
            'input[name="s"]',
            'input[data-seekmodo-typeahead]',
        ];
        $custom = apply_filters('seekmodo_typeahead_selectors', $defaults);
        if (!is_array($custom) || $custom === []) {
            return $defaults;
        }
        $clean = [];
        foreach ($custom as $sel) {
            if (is_string($sel) && $sel !== '') {
                $clean[] = $sel;
            }
        }

        return $clean === [] ? $defaults : $clean;
    }

    /**
     * @return array<string,string>
     */
    private static function i18n(): array
    {
        return [
            'no_results'   => function_exists('__') ? __('No results', 'seekmodo') : 'No results',
            'powered_by'   => function_exists('__') ? __('Powered by', 'seekmodo') : 'Powered by',
            'brand'        => 'Seekmodo',
            'on_sale'      => function_exists('__') ? __('Sale', 'seekmodo') : 'Sale',
            'out_of_stock' => function_exists('__') ? __('Out of stock', 'seekmodo') : 'Out of stock',
            'backorder'    => function_exists('__') ? __('On backorder', 'seekmodo') : 'On backorder',
        ];
    }
}
