<?php

namespace auctionforge\inc\helpers;

/**
 * Regional spelling dialect for the word "catalog" / "catalogue".
 *
 *   US dialect -> "Catalog"   (the plugin's authored strings, left unchanged)
 *   UK dialect -> "Catalogue"
 *
 * The front-end swap is performed with a `gettext` filter scoped to the
 * `auctionforge` text domain, so no template files need editing. Because the
 * filter only rewrites translated, user-facing strings it leaves every code
 * identifier, CSS class (e.g. `af-catalog-sidebar`, `catalog-section`),
 * JS variable and option key (e.g. `catalog_layout`, `bids_in_catalog`)
 * completely untouched.
 *
 * The dialect is chosen in: AuctionForge -> Settings -> Appearance.
 */
class Dialect
{
    const OPTION = 'spelling_dialect';
    const US = 'us';
    const UK = 'uk';
    const AUTO = 'auto';

    /**
     * Default mode for sites that have not chosen one: Auto (match the auction
     * house region). Resolves to FALLBACK_DIALECT until the first sync.
     */
    const DEFAULT_DIALECT = self::AUTO;

    /** Concrete fallback when Auto can't resolve yet, or for any unknown value. */
    const FALLBACK_DIALECT = self::UK;

    /** Auto mode: dialect resolved from the auction-house region (set at sync). */
    const OPTION_AUTO_RESOLVED = 'spelling_dialect_auto';      // 'us' | 'uk'
    const OPTION_AUTO_REGION   = 'spelling_dialect_auto_region'; // detected label, for admin display

    /** @var string|null Cached, normalised dialect for the current request. */
    private static $dialect = null;

    /**
     * Active dialect for this site: self::US or self::UK.
     * @return string
     */
    public static function get()
    {
        if (self::$dialect === null) {
            $value = SettingsHelper::getOption(self::OPTION, self::DEFAULT_DIALECT);
            if ($value === self::US) {
                self::$dialect = self::US;
            } elseif ($value === self::AUTO) {
                // Resolved from the auction-house region during sync; fall back to
                // a concrete dialect until the first sync populates it.
                $auto = get_option(self::OPTION_AUTO_RESOLVED, '');
                self::$dialect = ($auto === self::US) ? self::US : (($auto === self::UK) ? self::UK : self::FALLBACK_DIALECT);
            } else {
                self::$dialect = self::UK;
            }
        }
        return self::$dialect;
    }

    /**
     * @return bool True when the active dialect is UK ("Catalogue").
     */
    public static function isUk()
    {
        return self::get() === self::UK;
    }

    /**
     * Map an auction-house country (currency as a fallback) to a dialect. Only
     * the United States uses American "Catalog"; every other country uses
     * British "Catalogue".
     *
     * @param string $country  e.g. "United Kingdom", "United States", "USA"
     * @param string $currency e.g. "£", "$" — used only when country is unknown
     * @return string self::US | self::UK
     */
    public static function countryToDialect($country, $currency = '')
    {
        $c = strtolower(trim((string) $country));
        if ($c !== '') {
            foreach (array('united states', 'usa', 'u.s.a', 'u.s.', 'america') as $hint) {
                if (strpos($c, $hint) !== false) {
                    return self::US;
                }
            }
            if ($c === 'us') {
                return self::US;
            }
            return self::UK; // any other named country -> Catalogue
        }
        // No country in the address — best-effort from the currency symbol.
        return (strpos((string) $currency, '$') !== false) ? self::US : self::UK;
    }

    /**
     * Capture the auction-house region from a getAllAuctions response and store
     * the resolved "Auto" dialect. Called during the auction sync, so page loads
     * never trigger an API call. Harmless to run regardless of the chosen mode.
     *
     * @param array|object $auctions decoded `response` list of auctions
     */
    public static function captureRegionFromAuctions($auctions)
    {
        $country = '';
        $currency = '';
        foreach ((array) $auctions as $a) {
            $addr = is_object($a) ? (isset($a->address) ? $a->address : '') : (isset($a['address']) ? $a['address'] : '');
            $cur  = is_object($a) ? (isset($a->currency) ? $a->currency : '') : (isset($a['currency']) ? $a['currency'] : '');
            if ($currency === '' && $cur !== '') {
                $currency = $cur;
            }
            if ($addr !== '') {
                $parts = array_map('trim', explode(',', $addr));
                $country = (string) end($parts);
                if ($country !== '') {
                    break;
                }
            }
        }
        update_option(self::OPTION_AUTO_RESOLVED, self::countryToDialect($country, $currency), 'no');
        update_option(self::OPTION_AUTO_REGION, $country !== '' ? $country : trim('currency ' . $currency), 'no');
        self::$dialect = null; // invalidate per-request cache
    }

    /**
     * Return the catalog word in the active dialect, matching the casing and
     * pluralisation of $sample. Use this for any string that is NOT routed
     * through __() (the gettext filter handles everything that is).
     *
     * @param string $sample e.g. 'Catalog', 'catalog', 'CATALOG', 'Catalogs'
     * @return string
     */
    public static function word($sample = 'Catalog')
    {
        return self::isUk() ? self::toUk($sample) : $sample;
    }

    /**
     * `gettext` filter callback. Swaps US -> UK spelling in auctionforge
     * front-end strings.
     *
     * @param string $translation
     * @param string $text   (unused, kept for the filter signature)
     * @param string $domain
     * @return string
     */
    public static function filterGettext($translation, $text, $domain)
    {
        if ($domain !== 'auctionforge') {
            return $translation;
        }
        // Only the public-facing site, never the wp-admin settings UI. This
        // keeps admin field labels as authored and, importantly, keeps the
        // dialect dropdown's own "Catalog" / "Catalogue" option labels literal
        // so the choice stays self-explanatory. wp_doing_ajax() guards the
        // admin-ajax case (is_admin() is true there even for front-end AJAX).
        if (is_admin() && !wp_doing_ajax()) {
            return $translation;
        }
        return self::toUk($translation);
    }

    /**
     * `gettext_with_context` filter callback (4-arg signature).
     *
     * @param string $translation
     * @param string $text
     * @param string $context
     * @param string $domain
     * @return string
     */
    public static function filterGettextWithContext($translation, $text, $context, $domain)
    {
        return self::filterGettext($translation, $text, $domain);
    }

    /**
     * Replace whole-word "catalog"/"catalogs" with British "catalogue"/
     * "catalogues", preserving case (lower / Title / UPPER) and pluralisation.
     * Idempotent: input that already reads "catalogue" is returned unchanged,
     * and words such as "cataloged" / "subcatalog" are left alone.
     *
     * @param string $text
     * @return string
     */
    public static function toUk($text)
    {
        if ($text === '' || stripos($text, 'catalog') === false) {
            return $text;
        }
        return preg_replace_callback('/\bcatalog(ue)?(s)?\b/i', function ($m) {
            $matched = $m[0];
            $plural  = (substr(strtolower($matched), -1) === 's');
            $british = $plural ? 'catalogues' : 'catalogue';

            if ($matched === strtoupper($matched)) {
                return strtoupper($british);   // CATALOG(S) -> CATALOGUE(S)
            }
            if ($matched[0] === strtoupper($matched[0])) {
                return ucfirst($british);      // Catalog(S) -> Catalogue(s)
            }
            return $british;                   // catalog(s) -> catalogue(s)
        }, $text);
    }

    /**
     * Register the front-end gettext swap. No-op (zero added overhead) when the
     * active dialect is US, so US sites pay nothing.
     */
    public static function register()
    {
        if (!self::isUk()) {
            return;
        }
        add_filter('gettext', array(__CLASS__, 'filterGettext'), 10, 3);
        add_filter('gettext_with_context', array(__CLASS__, 'filterGettextWithContext'), 10, 4);
    }
}
