<?php

namespace SoftminalB2B\Compat;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Multilingual compatibility (WPML / Polylang).
 *
 * A translated product is a separate post with its own id, so a price, quantity
 * or BOGO rule created against the original would silently miss every
 * translation — the exact "rules don't apply in other languages" complaint that
 * dogs incumbents. This maps any translated product or category id back to the
 * default language, and the rule engines key everything off that. Rules are also
 * stored against the default-language id when saved, so a rule is language-
 * agnostic from both ends: whatever language it was created in, it applies in
 * all of them.
 *
 * When no multilingual plugin is active every method is a cheap pass-through —
 * one memoised boolean check and the id comes straight back.
 */
class Multilingual
{
    private static ?bool $active = null;

    /** @var array<string, int> "kind:type:id" => default-language id */
    private static array $memo = [];

    /**
     * Is a multilingual plugin we understand active? Filterable so tests (and
     * plugins we don't detect natively) can opt in.
     */
    public static function isActive(): bool
    {
        if (self::$active === null) {
            $native = defined('ICL_SITEPRESS_VERSION') || function_exists('pll_default_language');

            self::$active = (bool) apply_filters('smb2b_multilingual_active', $native);
        }

        return self::$active;
    }

    /** Reset the cached active flag + map (tests, language switches). */
    public static function flush(): void
    {
        self::$active = null;
        self::$memo   = [];
    }

    /**
     * The default-language id for a product/variation, or the id unchanged when
     * nothing is translated.
     */
    public static function productId(int $id): int
    {
        if ($id <= 0 || !self::isActive()) {
            return $id;
        }

        $postType = get_post_type($id) ?: 'product';

        return self::map($id, 'post', $postType);
    }

    /**
     * @param array<int, int> $ids
     * @return array<int, int>
     */
    public static function productIds(array $ids): array
    {
        if (!self::isActive()) {
            return $ids;
        }

        $mapped = array_map([self::class, 'productId'], $ids);

        return array_values(array_unique(array_filter($mapped)));
    }

    /**
     * The default-language id for a taxonomy term.
     */
    public static function termId(int $id, string $taxonomy = 'product_cat'): int
    {
        if ($id <= 0 || !self::isActive()) {
            return $id;
        }

        return self::map($id, 'term', $taxonomy);
    }

    /**
     * @param array<int, int> $ids
     * @return array<int, int>
     */
    public static function termIds(array $ids, string $taxonomy = 'product_cat'): array
    {
        if (!self::isActive()) {
            return $ids;
        }

        $mapped = [];

        foreach ($ids as $id) {
            $mapped[] = self::termId((int) $id, $taxonomy);
        }

        return array_values(array_unique(array_filter($mapped)));
    }

    /**
     * Map one element to its default-language equivalent, memoised per request.
     */
    private static function map(int $id, string $kind, string $type): int
    {
        $key = $kind . ':' . $type . ':' . $id;

        if (isset(self::$memo[$key])) {
            return self::$memo[$key];
        }

        return self::$memo[$key] = self::resolveDefault($id, $kind, $type);
    }

    private static function resolveDefault(int $id, string $kind, string $type): int
    {
        // WPML / WooCommerce Multilingual.
        if (defined('ICL_SITEPRESS_VERSION')) {
            // WPML's own hooks — consumed here, not defined by this plugin.
            $default     = apply_filters('wpml_default_language', null); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
            $elementType = $kind === 'term' ? 'tax_' . $type : 'post_' . $type;
            $mapped      = apply_filters('wpml_object_id', $id, $elementType, true, $default); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

            if (is_numeric($mapped) && (int) $mapped > 0) {
                return (int) $mapped;
            }
        }

        // Polylang.
        if ($kind === 'post' && function_exists('pll_get_post') && function_exists('pll_default_language')) {
            $mapped = pll_get_post($id, pll_default_language());

            if (is_numeric($mapped) && (int) $mapped > 0) {
                return (int) $mapped;
            }
        }

        if ($kind === 'term' && function_exists('pll_get_term') && function_exists('pll_default_language')) {
            $mapped = pll_get_term($id, pll_default_language());

            if (is_numeric($mapped) && (int) $mapped > 0) {
                return (int) $mapped;
            }
        }

        /**
         * Generic mapping seam for multilingual plugins we don't detect
         * natively — and the hook tests drive.
         *
         * @param int    $id
         * @param string $kind 'post' | 'term'
         * @param string $type post type or taxonomy
         */
        $mapped = apply_filters('smb2b_default_language_id', $id, $kind, $type);

        return is_numeric($mapped) && (int) $mapped > 0 ? (int) $mapped : $id;
    }
}
