<?php

namespace DevOwl\RealCategoryLibrary\Vendor\DevOwl\Multilingual;

use WP_Post;
use WP_Term;
// @codeCoverageIgnoreStart
\defined('ABSPATH') or die('No script kiddies please!');
// Avoid direct file request
// @codeCoverageIgnoreEnd
/**
 * WPML language handler.
 * @internal
 */
class WPML extends AbstractSyncPlugin
{
    private $disabledCopyAndSync = \false;
    /**
     * True while `sitepress->make_duplicate()` runs (Translation Dashboard duplicate).
     * `Sync::save_post` must not create extra language copies during this window.
     */
    private $duplicateCreationInProgress = \false;
    /**
     * True while WPML taxonomy translation UI saves a term (`wp_ajax_wpml_save_term`).
     * `Sync::created_term` must not spawn copies in other languages during this window.
     */
    private $wpmlTermSaveInProgress = \false;
    /**
     * Per-master taxonomy assignments captured at `icl_before_make_duplicate`.
     *
     * WPML can clear or rewrite `wp_term_relationships` on the master post before DevOwl
     * `copyPostTaxonomies()` runs. The snapshot preserves ids that were still on the master at
     * duplicate start (typical stack: DE translation still wired to EN `rcb-cookie-group` term ids).
     * Consumed once when resolving source terms during {@see resolveSourceTermsByTaxonomyForPostCopy()}.
     *
     * @var array<int, array<string, int[]>> master post id => taxonomy => term ids
     */
    private $duplicateSourceTermsByMaster = [];
    // Documented in AbstractSyncPlugin
    public function switch($locale)
    {
        global $sitepress;
        $sitepress->switch_lang($locale);
    }
    // Documented in AbstractSyncPlugin
    public function copyTermToOtherLanguage($locale, $currentLanguage, $term_id, $taxonomy, $meta)
    {
        global $sitepress;
        $createdTermId = parent::copyTermToOtherLanguage($locale, $currentLanguage, $term_id, $taxonomy, $meta);
        if (!$createdTermId) {
            return \false;
        }
        // Create term translation (https://wordpress.stackexchange.com/a/309046/83335)
        $originalElementId = \get_term($term_id)->term_taxonomy_id;
        $createdElementId = \get_term($createdTermId)->term_taxonomy_id;
        $trid = $sitepress->get_element_trid($originalElementId, 'tax_' . $taxonomy);
        $sitepress->set_element_language_details($createdElementId, 'tax_' . $taxonomy, $trid, $locale, $this->getSourceLanguage($term_id, $taxonomy, $currentLanguage));
        return $createdTermId;
    }
    // Documented in AbstractSyncPlugin
    public function copyPostToOtherLanguage($locale, $currentLanguage, $post_id, $meta, $taxonomies)
    {
        global $sitepress;
        // Read post
        $post = \get_post($post_id);
        // Create
        $created = parent::copyPostToOtherLanguage($locale, $currentLanguage, $post_id, $meta, $taxonomies);
        if (!$created) {
            return \false;
        }
        // WPML has an issue that the `trid` is not yet known directly in `save_post` (https://wpml.org/forums/topic/get_element_trid-returns-null/)
        // So, the posts are linked through the `shutdown` action.
        \add_action('shutdown', function () use($sitepress, $post, $post_id, $created, $locale, $currentLanguage) {
            $trid = $sitepress->get_element_trid($post_id, 'post_' . $post->post_type);
            $sitepress->set_element_language_details($created, 'post_' . $post->post_type, $trid, $locale, $this->getSourceLanguage($post_id, $post->post_type, $currentLanguage));
        });
        return $created;
    }
    // Documented in AbstractLanguagePlugin
    public function getActiveLanguages()
    {
        return \array_keys(\apply_filters('wpml_active_languages', []));
    }
    // Documented in AbstractLanguagePlugin
    public function getLanguageSwitcher()
    {
        $result = [];
        foreach (\apply_filters('wpml_active_languages', [], ['skip_missing' => 0]) as $row) {
            $result[] = ['name' => $row['native_name'], 'current' => \boolval($row['active'] ?? \false), 'flag' => $row['country_flag_url'] ?? '', 'url' => $row['url'], 'locale' => $row['language_code']];
        }
        return $result;
    }
    // Documented in AbstractLanguagePlugin
    public function getTranslatedName($locale)
    {
        $activeLanguages = \apply_filters('wpml_active_languages', []);
        return isset($activeLanguages[$locale]) ? $activeLanguages[$locale]['translated_name'] : $locale;
    }
    // Documented in AbstractLanguagePlugin
    public function getCountryFlag($locale)
    {
        $settings = \apply_filters('wpml_active_languages', []);
        if (isset($settings[$locale])) {
            return $settings[$locale]['country_flag_url'] ?? \false;
        }
        return \false;
    }
    // Documented in AbstractLanguagePlugin
    public function getPermalink($url, $locale)
    {
        return \apply_filters('wpml_permalink', $url, $locale, \true);
    }
    // Documented in AbstractLanguagePlugin
    public function getWordPressCompatibleLanguageCode($locale)
    {
        $objects = \apply_filters('wpml_active_languages', []);
        return isset($objects[$locale]) ? $objects[$locale]['default_locale'] : $locale;
    }
    // Documented in AbstractLanguagePlugin
    public function getDefaultLanguage()
    {
        global $sitepress;
        return $sitepress->get_default_language();
    }
    // Documented in AbstractLanguagePlugin
    public function getCurrentLanguage()
    {
        global $sitepress;
        return $sitepress->get_current_language();
    }
    // Documented in AbstractLanguagePlugin
    public function getPostLanguage($id)
    {
        $result = \apply_filters('wpml_post_language_details', '', $id);
        return \is_array($result) ? $result['language_code'] : '';
    }
    // Documented in AbstractLanguagePlugin
    public function getTermLanguage($termTaxonomyId, $taxonomy)
    {
        $result = \apply_filters('wpml_element_language_details', null, ['element_id' => $termTaxonomyId, 'element_type' => 'tax_' . $taxonomy]);
        return \is_object($result) && isset($result->language_code) ? $result->language_code : '';
    }
    // Documented in AbstractSyncPlugin
    public function isExternalTranslationSyncDeferredForPost($postId)
    {
        // Only while `make_duplicate` runs — not for posts that still had `_icl_lang_duplicate_of`
        // (we remove that meta after `syncPostFromExternalSource` so DevOwl Sync owns updates).
        return $this->duplicateCreationInProgress;
    }
    /**
     * Resolve source taxonomy ids for {@see AbstractSyncPlugin::copyPostTaxonomies()} (WPML).
     *
     * Three fallbacks, first non-empty wins:
     *
     * 1. **Live read** ({@see AbstractSyncPlugin::resolveSourceTermsByTaxonomyForPostCopy()}):
     *    `fetchObjectTermIdsWithoutFilters()` on `$from`. Succeeds when relationships are still on
     *    the master at copy time (normal duplicate, or cross-language term ids still stored).
     *
     * 2. **Duplicate snapshot** (`$duplicateSourceTermsByMaster`): ids captured on `$from` in
     *    `icl_before_make_duplicate` before WPML clears the master, then unset here. Needed when
     *    step 1 is empty because WPML already removed assignments (common for DE → EN duplicate of
     *    a DE cookie wired to EN group terms). Empty when no taxonomies were on the master at hook
     *    time or this master was not part of the current duplicate run.
     *
     * 3. **Translation sibling** (trid scan below): read from another post in the same WPML trid
     *    when steps 1 and 2 are empty, but a sibling translation still has assignments (e.g. master
     *    already cleared, EN row not deleted yet). Does **not** help when every translation in the
     *    trid has empty taxonomies (e.g. EN master removed and only DE remains) — then step 2 must
     *    have captured terms at `icl_before_make_duplicate`. Snapshot fixes “WPML cleared the master
     *    between hook and copy”; siblings fix “master empty, another language row still has groups”.
     *
     * @param int $from Source post id (WPML master for the duplicate being built)
     * @param string[] $taxonomies Taxonomy slugs from Sync post configuration
     * @return array<string, int[]> taxonomy slug => term ids to remap onto the target locale
     */
    protected function resolveSourceTermsByTaxonomyForPostCopy($from, array $taxonomies)
    {
        $sourceTermsByTaxonomy = parent::resolveSourceTermsByTaxonomyForPostCopy($from, $taxonomies);
        if ($sourceTermsByTaxonomy !== []) {
            return $sourceTermsByTaxonomy;
        }
        $from = (int) $from;
        $sourceTermsByTaxonomy = $this->duplicateSourceTermsByMaster[$from] ?? [];
        unset($this->duplicateSourceTermsByMaster[$from]);
        if ($sourceTermsByTaxonomy !== []) {
            return $sourceTermsByTaxonomy;
        }
        global $sitepress;
        $post = \get_post($from);
        if (!isset($sitepress) || !$post instanceof WP_Post) {
            return [];
        }
        $elementType = 'post_' . $post->post_type;
        $trid = (int) $sitepress->get_element_trid($from, $elementType);
        if ($trid <= 0) {
            return [];
        }
        $translations = $sitepress->get_element_translations($trid, $elementType);
        if (!\is_array($translations)) {
            return [];
        }
        $excludePostId = (int) ($this->taxonomySourceResolutionExcludePostId ?? 0);
        foreach ($translations as $translation) {
            $altPostId = isset($translation->element_id) ? (int) $translation->element_id : 0;
            if ($altPostId <= 0 || $altPostId === $from || $excludePostId > 0 && $altPostId === $excludePostId) {
                continue;
            }
            $sourceTermsByTaxonomy = parent::resolveSourceTermsByTaxonomyForPostCopy($altPostId, $taxonomies);
            if ($sourceTermsByTaxonomy !== []) {
                return $sourceTermsByTaxonomy;
            }
        }
        return [];
    }
    // Documented in AbstractSyncPlugin
    public function isExternalTranslationSyncDeferredForTerm($term_id, $taxonomy)
    {
        if ($this->wpmlTermSaveInProgress) {
            return \true;
        }
        $term = \get_term($term_id, $taxonomy);
        if (!$term instanceof WP_Term) {
            return \false;
        }
        $details = \apply_filters('wpml_element_language_details', null, ['element_id' => $term->term_taxonomy_id, 'element_type' => 'tax_' . $taxonomy]);
        return \is_object($details) && !empty($details->source_language_code);
    }
    // Documented in AbstractSyncPlugin
    public function getOriginalPostId($id, $post_type)
    {
        return \intval(\icl_object_id($id, $post_type, \false, $this->getDefaultLanguage()));
    }
    // Documented in AbstractSyncPlugin
    public function getOriginalTermId($id, $taxonomy)
    {
        return \intval(\icl_object_id($id, $taxonomy, \false, $this->getDefaultLanguage()));
    }
    // Documented in AbstractLanguagePlugin
    public function getPostTranslationIds($id, $post_type)
    {
        $result = [];
        $trid = \apply_filters('wpml_element_trid', null, $id, 'post_' . $post_type);
        $translations = \apply_filters('wpml_get_element_translations', null, $trid, 'post_' . $post_type);
        if (\is_array($translations)) {
            foreach ($translations as $translation) {
                $result[$translation->language_code] = \intval($translation->element_id);
            }
        }
        return $result;
    }
    // Documented in AbstractLanguagePlugin
    public function getTaxonomyTranslationIds($id, $taxonomy)
    {
        $result = [];
        $trid = \apply_filters('wpml_element_trid', null, $id, 'tax_' . $taxonomy);
        $translations = \apply_filters('wpml_get_element_translations', null, $trid, 'tax_' . $taxonomy);
        if (\is_array($translations)) {
            foreach ($translations as $translation) {
                $result[$translation->language_code] = \intval($translation->element_id);
            }
        }
        return $result;
    }
    // Documented in AbstractSyncPlugin
    public function getCurrentPostId($id, $post_type, $locale = null)
    {
        if ($locale !== null && isset($this->copyToOtherLanguageMap['post'][$locale])) {
            $map = $this->copyToOtherLanguageMap['post'][$locale];
            if (isset($map[$id])) {
                return $map[$id];
            }
        }
        // When `$locale` is set (meta remap, REST), do not fall back to the source post id — same rule as
        // `getCurrentTermId()` (`return_original` false). `true` is only for "current language" lookups.
        $returnOriginal = $locale === null;
        return \intval(\icl_object_id($id, $post_type, $returnOriginal, $locale === null ? $this->getCurrentLanguage() : $locale));
    }
    // Documented in AbstractSyncPlugin
    public function getCurrentTermId($id, $taxonomy, $locale = null)
    {
        if ($locale !== null && isset($this->copyToOtherLanguageMap['term'][$locale])) {
            $map = $this->copyToOtherLanguageMap['term'][$locale];
            if (isset($map[$id])) {
                return $map[$id];
            }
        }
        $lang = $locale === null ? $this->getCurrentLanguage() : $locale;
        // `true` falls back to the source term id in another language (wrong group on DE cookies).
        // Callers that need a real translation compare `$resolved !== $sourceTermId` before assigning.
        $resolved = \intval(\icl_object_id($id, $taxonomy, \false, $lang));
        return $resolved > 0 ? $resolved : $id;
    }
    /**
     * Get the source language of a given element id and element type.
     *
     * @param int $element_id
     * @param string $element_type
     * @param mixed $fallback
     */
    protected function getSourceLanguage($element_id, $element_type, $fallback = null)
    {
        $details = \apply_filters('wpml_element_language_details', null, ['element_id' => $element_id, 'element_type' => $element_type]);
        if (\is_object($details) && !empty($details->source_language_code)) {
            return $details->source_language_code;
        }
        if (\is_array($details) && !empty($details['source_language_code'])) {
            return $details['source_language_code'];
        }
        return $fallback;
    }
    // Documented in AbstractLanguagePlugin
    public function disableCopyAndSync($sync)
    {
        if ($this->disabledCopyAndSync) {
            return;
        }
        $this->disabledCopyAndSync = \true;
        // WPML Translation Dashboard "Duplicate" copies post meta via SQL and runs
        // `wpml_duplicate_generic_string`, which can empty translatable custom fields.
        // Our Sync config marks fields as copy/copy-once — preserve the master value.
        \add_filter('wpml_duplicate_generic_string', function ($value, $lang, $meta_data) use($sync) {
            if (($meta_data['context'] ?? '') !== 'custom_field') {
                return $value;
            }
            $meta_key = $meta_data['key'] ?? '';
            $master_post_id = (int) ($meta_data['master_post_id'] ?? 0);
            if ($master_post_id <= 0 || $meta_key === '') {
                return $value;
            }
            $copyKeys = $this->getConfiguredPostMetaCopyKeys($sync, $master_post_id);
            if ($copyKeys === null || !\in_array($meta_key, $copyKeys, \true)) {
                return $value;
            }
            if (!\metadata_exists('post', $master_post_id, $meta_key)) {
                return $value;
            }
            return \get_post_meta($master_post_id, $meta_key, \true);
        }, 999, 3);
        \add_action('wpml_before_make_duplicate', function () {
            $this->duplicateCreationInProgress = \true;
        }, 0, 0);
        // Snapshot master taxonomy ids before WPML clears relationships (see
        // resolveSourceTermsByTaxonomyForPostCopy() step 2).
        \add_action('icl_before_make_duplicate', function ($master_post_id) use($sync) {
            $this->duplicateCreationInProgress = \true;
            $post = \get_post($master_post_id);
            if (!$post instanceof WP_Post) {
                return;
            }
            $configuration = $sync->getPostsConfiguration()[$post->post_type] ?? null;
            if ($configuration === null) {
                return;
            }
            $taxonomies = $configuration['taxonomies'] ?? [];
            if ($taxonomies === []) {
                return;
            }
            $snapshot = [];
            foreach ($taxonomies as $taxonomy) {
                $termIds = $this->fetchObjectTermIdsWithoutFilters((int) $master_post_id, $taxonomy);
                if ($termIds !== []) {
                    $snapshot[$taxonomy] = $termIds;
                }
            }
            if ($snapshot !== []) {
                $this->duplicateSourceTermsByMaster[(int) $master_post_id] = $snapshot;
            }
        }, 0, 2);
        \add_action('icl_make_duplicate', function ($master_post_id, $lang, $post_array, $duplicate_id) use($sync) {
            // WPML has already duplicated the post and SQL-copied meta. We only add taxonomy
            // remapping + translated title/content — not a second full meta copy (RCB JSON meta
            // can be megabytes and was a contributor to TM duplicate OOM / emptied fields).
            $post = \get_post($master_post_id);
            if (!$post instanceof WP_Post) {
                return;
            }
            $configuration = $sync->getPostsConfiguration()[$post->post_type] ?? null;
            if ($configuration === null) {
                return;
            }
            $taxonomies = $configuration['taxonomies'] ?? [];
            $this->syncPostFromExternalSource($master_post_id, $duplicate_id, $lang, $taxonomies, $configuration);
            // Same detach as `wpml_copy_post_to_language` with `$mark_as_duplicate === false`:
            // stops WPML `sync_with_duplicates` from mirroring master saves onto this translation.
            \delete_post_meta($duplicate_id, '_icl_lang_duplicate_of');
        }, 20, 4);
        \add_action('icl_make_duplicate', function () {
            $this->duplicateCreationInProgress = \false;
        }, 9999, 0);
        \add_action('wp_ajax_wpml_save_term', [$this, 'beforeWpmlSaveTerm'], 0);
        \add_action('wp_ajax_wpml_save_term', [$this, 'endWpmlSaveTerm'], 9999, 0);
        \add_action('created_term', [$this, 'syncTermAfterWpmlTranslation'], 20, 3);
    }
    /**
     * Mark WPML taxonomy term save so `Sync::created_term` does not recurse.
     */
    public function beforeWpmlSaveTerm()
    {
        $this->wpmlTermSaveInProgress = \true;
    }
    /**
     * Clear the WPML taxonomy save flag after the AJAX handler finishes.
     */
    public function endWpmlSaveTerm()
    {
        $this->wpmlTermSaveInProgress = \false;
    }
    /**
     * After WPML linked a new term translation, apply PO translations and copy meta from the source term.
     *
     * Runs on `created_term` (priority 20), after WPML's `create_term` language assignment (priority 1).
     * Uses `wpml_element_language_details` instead of `$_POST` so CLI/API paths work too.
     *
     * @param int $term_id
     * @param int $tt_id
     * @param string $taxonomy
     */
    public function syncTermAfterWpmlTranslation($term_id, $tt_id, $taxonomy)
    {
        $sync = $this->getSync();
        if ($sync === null) {
            return;
        }
        $configuration = $sync->getTaxonomies()[$taxonomy] ?? null;
        if ($configuration === null) {
            return;
        }
        $term = \get_term($term_id, $taxonomy);
        if (!$term instanceof WP_Term) {
            return;
        }
        $elementType = 'tax_' . $taxonomy;
        $details = \apply_filters('wpml_element_language_details', null, ['element_id' => $term->term_taxonomy_id, 'element_type' => $elementType]);
        if (!\is_object($details) || empty($details->source_language_code)) {
            return;
        }
        $sourceLocale = $details->source_language_code;
        $targetLocale = isset($details->language_code) ? $details->language_code : '';
        if ($targetLocale === '' || $sourceLocale === $targetLocale) {
            return;
        }
        global $sitepress;
        $trid = isset($details->trid) ? (int) $details->trid : 0;
        if ($trid <= 0) {
            $trid = (int) $sitepress->get_element_trid($term->term_taxonomy_id, $elementType);
        }
        if ($trid <= 0) {
            return;
        }
        $translations = $sitepress->get_element_translations($trid, $elementType);
        if (!\is_array($translations)) {
            return;
        }
        $sourceTtid = isset($translations[$sourceLocale]) ? (int) $translations[$sourceLocale]->element_id : 0;
        $targetTtid = isset($translations[$targetLocale]) ? (int) $translations[$targetLocale]->element_id : 0;
        if ($sourceTtid <= 0 || $targetTtid <= 0) {
            return;
        }
        $sourceTerm = \get_term_by('term_taxonomy_id', $sourceTtid, $taxonomy);
        $targetTerm = \get_term_by('term_taxonomy_id', $targetTtid, $taxonomy);
        if (!$sourceTerm instanceof WP_Term || !$targetTerm instanceof WP_Term) {
            return;
        }
        $metaKeys = \array_unique(\array_merge($configuration['meta']['copy-once'] ?? [], $configuration['meta']['copy'] ?? []));
        $this->syncTermFromExternalSource($sourceTerm->term_id, $targetTerm->term_id, $metaKeys, $targetLocale);
    }
    /**
     * Meta keys configured for copy sync on the post's type, if any.
     *
     * @param Sync $sync
     * @param int $post_id
     * @return string[]|null
     */
    private function getConfiguredPostMetaCopyKeys($sync, $post_id)
    {
        $post = \get_post($post_id);
        if (!$post instanceof WP_Post) {
            return null;
        }
        $configuration = $sync->getPostsConfiguration()[$post->post_type] ?? null;
        if ($configuration === null) {
            return null;
        }
        return \array_unique(\array_merge($configuration['meta']['copy-once'] ?? [], $configuration['meta']['copy'] ?? []));
    }
    /**
     * Check if WPML is active.
     */
    public static function isPresent()
    {
        return \is_plugin_active('sitepress-multilingual-cms/sitepress.php');
    }
}
