<?php

declare(strict_types=1);

namespace CliqThemes\StoreLocator\I18n;

defined('ABSPATH') || exit;

// These hooks are defined by WPML and therefore intentionally use WPML's prefix.
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

/**
 * Language resolution for the multilingual (TRID) content model.
 *
 * Works under BOTH WPML and Polylang by detecting whichever is active and
 * reading the current/default/available languages from it. When neither plugin
 * is active (or the current language is the site default) this resolver yields
 * an empty string, which means "use the original rows" — so the whole plugin
 * behaves exactly as a single-language install with zero overhead.
 *
 * Convention: the canonical/default-language record is stored with lang='' and
 * source_id=NULL (the "original"). Translations are sibling rows sharing the
 * original's `trid`, carrying their own `lang` and source_id=original id, and
 * holding ONLY translatable text (name/description). All language-neutral data
 * (coordinates, hours, category/brand links, status) lives on the original.
 */
final class Multilingual
{
    /**
     * Explicit override (admin language switcher / shortcode lang="…").
     * Null = not set; '' = forced default. Takes precedence over auto-detect.
     */
    private static ?string $override = null;

    /** Force the active language (normalised). Pass '' for the default. */
    public static function setOverride(?string $lang): void
    {
        self::$override = $lang === null ? null : self::normalize($lang);
    }

    /** Is a supported multilingual plugin active? */
    public static function isActive(): bool
    {
        return self::hasPolylang() || self::hasWpml();
    }

    private static function hasPolylang(): bool
    {
        return function_exists('pll_current_language') && function_exists('pll_default_language');
    }

    private static function hasWpml(): bool
    {
        return defined('ICL_SITEPRESS_VERSION') || function_exists('icl_object_id');
    }

    /**
     * The active language as a normalised code, or '' for the site default /
     * single-language. Callers store this in the `lang` column and filter on it.
     */
    public static function current(): string
    {
        if (self::$override !== null) {
            return self::$override;
        }

        if (! self::isActive()) {
            return '';
        }

        $locale = '';

        if (self::hasPolylang()) {
            $locale = (string) (pll_current_language('locale') ?: '');
        } elseif (self::hasWpml()) {
            $code = (string) (apply_filters('wpml_current_language', null) ?: '');
            $locale = self::wpmlCodeToLocale($code);
        }

        if ($locale === '') {
            $locale = (string) get_locale();
        }

        return self::isDefault($locale) ? '' : self::normalize($locale);
    }

    /** The site's default language locale (normalised), or '' if unknown. */
    public static function defaultLocale(): string
    {
        if (self::hasPolylang()) {
            return self::normalize((string) (pll_default_language('locale') ?: ''));
        }

        if (self::hasWpml()) {
            $code = (string) (apply_filters('wpml_default_language', null) ?: '');
            return self::normalize(self::wpmlCodeToLocale($code));
        }

        return '';
    }

    /** True if the given locale is the site default (treated as the original). */
    private static function isDefault(string $locale): bool
    {
        $locale = self::normalize($locale);
        if ($locale === '') {
            return true;
        }

        $default = self::defaultLocale();
        if ($default !== '' && $locale === $default) {
            return true;
        }

        // Fallback: treat plain English as the default when nothing else says so.
        return $default === '' && in_array($locale, ['en', 'en_us'], true);
    }

    /**
     * Active languages for the admin switcher.
     *
     * @return array<int, array{code:string,label:string,is_default:bool,is_current:bool}>
     */
    public static function languages(): array
    {
        $out = [];
        $current = self::current();

        if (self::hasPolylang()) {
            $locales = (array) (pll_languages_list(['fields' => 'locale']) ?: []);
            $names = (array) (pll_languages_list(['fields' => 'name']) ?: []);
            $default = self::defaultLocale();
            foreach ($locales as $i => $loc) {
                $norm = self::normalize((string) $loc);
                $code = ($norm === $default || self::isDefault((string) $loc)) ? '' : $norm;
                $out[] = [
                    'code' => $code,
                    'label' => (string) ($names[$i] ?? $loc),
                    'is_default' => $code === '',
                    'is_current' => $code === $current,
                ];
            }
        } elseif (self::hasWpml()) {
            $langs = (array) (apply_filters('wpml_active_languages', null, ['skip_missing' => 0]) ?: []);
            foreach ($langs as $lang) {
                $loc = (string) ($lang['default_locale'] ?? $lang['language_code'] ?? '');
                $norm = self::normalize($loc);
                $code = self::isDefault($loc) ? '' : $norm;
                $out[] = [
                    'code' => $code,
                    'label' => (string) ($lang['translated_name'] ?? $lang['native_name'] ?? $loc),
                    'is_default' => $code === '',
                    'is_current' => $code === $current,
                ];
            }
        }

        return $out;
    }

    /** Human label for a normalised language code (from the active plugin), or ''. */
    public static function label(string $code): string
    {
        $code = self::normalize($code);
        if ($code === '') {
            return '';
        }
        foreach (self::languages() as $lang) {
            if (($lang['code'] ?? '') === $code) {
                return (string) $lang['label'];
            }
        }

        return $code;
    }

    /** Lower-case, hyphen→underscore (e.g. "fr-FR" → "fr_fr"). Max 10 chars. */
    public static function normalize(string $lang): string
    {
        $lang = strtolower(str_replace('-', '_', trim($lang)));
        return substr($lang, 0, 10);
    }

    /** Map a WPML language code (e.g. "fr") to a locale using its language table. */
    private static function wpmlCodeToLocale(string $code): string
    {
        if ($code === '') {
            return '';
        }

        $langs = (array) (apply_filters('wpml_active_languages', null, ['skip_missing' => 0]) ?: []);
        foreach ($langs as $lang) {
            if (($lang['language_code'] ?? null) === $code) {
                return (string) ($lang['default_locale'] ?? $code);
            }
        }

        return $code;
    }
}
