<?php

if (! defined('ABSPATH')) {
    exit;
} final class PufferGo_Shared_Slug
{
    private static $scoped_lang = '';

    private static $intended_term_slug = '';

    private static $path_post_cache = array();

    public static function scope_language(string $lang, callable $fn, string $term_slug = '')
    {
        $prev_lang = self::$scoped_lang;
        $prev_slug = self::$intended_term_slug;
        self::$scoped_lang = $lang;

        if ('' !== $term_slug) {
            self::$intended_term_slug = sanitize_title($term_slug);
        }

try {
            return $fn();
        } finally {
            self::$scoped_lang = $prev_lang;
            self::$intended_term_slug = $prev_slug;
        }
    }

    public static function init(): void
    {
        add_filter('wp_unique_post_slug', array(__CLASS__, 'unique_post_within_language'), 999, 6);
        add_filter('wp_unique_term_slug', array(__CLASS__, 'unique_term_within_language'), 999, 3);
        add_filter('wp_unique_term_slug_is_bad_slug', array(__CLASS__, 'term_slug_not_bad_in_language'), 999, 3);
        add_filter('wp_insert_term_data', array(__CLASS__, 'apply_term_slug_on_insert'), 999, 3);
        add_filter('wp_update_term_data', array(__CLASS__, 'apply_term_slug_on_update'), 999, 4);
        add_filter('wp_insert_term_duplicate_term_check', array(__CLASS__, 'allow_cross_language_term_duplicate'), 999, 3);
        add_action('pre_get_posts', array(__CLASS__, 'fix_main_query_for_shared_slug'), 1);
        add_action('template_redirect', array(__CLASS__, 'suppress_canonical_for_shared_slug'), 0);
        add_filter('redirect_canonical', array(__CLASS__, 'block_shared_slug_redirect'), 999, 2);
        add_filter('pll_check_canonical_url', array(__CLASS__, 'block_polylang_canonical'), 999, 2);
        add_filter('posts_join', array(__CLASS__, 'join_language'), 10, 2);
        add_filter('posts_where', array(__CLASS__, 'where_language'), 10, 2);
        add_action('parse_request', array(__CLASS__, 'enable_page_query_filter'), 0);
    }

    public static function enable_page_query_filter($wp): void
    {
        if (! self::is_frontend_content_request()) {
            return;
        }

if (is_object($wp) && ! empty($wp->query_vars['feed'])) {
            return;
        } add_filter('query', array(__CLASS__, 'filter_page_query'));
    }

    public static function unique_post_within_language($slug, $post_id, $post_status, $post_type, $post_parent, $original_slug): string
    {
        global $wpdb;

        if (! self::polylang_active() || $slug === $original_slug) {
            return (string) $slug;
        } $lang = self::post_save_language((int) $post_id);

        if ('' === $lang) {
            return (string) $slug;
        } $join_clause = self::post_join_clause();
        $where_clause = self::post_where_clause($lang);

        if ('attachment' === $post_type) {
            $sql = "SELECT ID FROM $wpdb->posts $join_clause WHERE post_name = %s AND post_type = 'attachment' AND ID != %d $where_clause LIMIT 1";
            $check = $wpdb->get_var($wpdb->prepare($sql, $original_slug, $post_id));
        } elseif (is_post_type_hierarchical($post_type)) {
            $sql = "SELECT ID FROM $wpdb->posts $join_clause WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d $where_clause LIMIT 1";
            $check = $wpdb->get_var($wpdb->prepare($sql, $original_slug, $post_type, $post_id, $post_parent));
        } else {
            $sql = "SELECT post_name FROM $wpdb->posts $join_clause WHERE post_name = %s AND post_type = %s AND ID != %d $where_clause LIMIT 1";
            $check = $wpdb->get_var($wpdb->prepare($sql, $original_slug, $post_type, $post_id));
        }

return $check ? (string) $slug : (string) $original_slug;
    }

    public static function unique_term_within_language($slug, $term, $original_slug): string
    {
        $slug = (string) $slug;
        $original_slug = (string) $original_slug;

        if (! self::polylang_active() || $slug === $original_slug || ! is_object($term)) {
            return $slug;
        } $taxonomy = isset($term->taxonomy) ? (string) $term->taxonomy : '';

        if ('' === $taxonomy || ! self::is_translated_taxonomy($taxonomy)) {
            return $slug;
        } $term_id = isset($term->term_id) ? (int) $term->term_id : 0;
        $target = self::resolve_intended_term_slug();

        if ('' === $target) {
            $target = $original_slug;
        }

return self::should_use_term_slug($target, $term_id, $taxonomy, self::term_parent_from_object($term)) ? $target : $slug;
    }

    public static function term_slug_not_bad_in_language($needs_suffix, $slug, $term): bool
    {
        if (! $needs_suffix || ! self::polylang_active() || ! is_object($term)) {
            return (bool) $needs_suffix;
        } $taxonomy = isset($term->taxonomy) ? (string) $term->taxonomy : '';

        if ('' === $taxonomy || ! self::is_translated_taxonomy($taxonomy)) {
            return (bool) $needs_suffix;
        } $term_id = isset($term->term_id) ? (int) $term->term_id : 0;
        $target = self::resolve_intended_term_slug();

        if ('' === $target) {
            $target = (string) $slug;
        }

return self::should_use_term_slug($target, $term_id, $taxonomy, self::term_parent_from_object($term)) ? false : (bool) $needs_suffix;
    }

    public static function apply_term_slug_on_insert($data, $taxonomy, $args)
    {
        return self::apply_intended_term_slug((array) $data, 0, (string) $taxonomy, is_array($args) ? $args : array());
    }

    public static function apply_term_slug_on_update($data, $term_id, $taxonomy, $args)
    {
        return self::apply_intended_term_slug((array) $data, (int) $term_id, (string) $taxonomy, is_array($args) ? $args : array());
    }

    public static function allow_cross_language_term_duplicate($duplicate_term, $term, $taxonomy)
    {
        unset($term);

        if (! $duplicate_term || ! self::polylang_active() || ! self::is_translated_taxonomy($taxonomy)) {
            return $duplicate_term;
        } $lang = self::$scoped_lang;

        if ('' === $lang) {
            return $duplicate_term;
        } $duplicate_lang = PufferGo_Polylang::get_term_language_fresh((int) $duplicate_term->term_id);

        return ('' !== $duplicate_lang && $duplicate_lang !== $lang) ? false : $duplicate_term;
    }

    public static function join_language(string $join, $query): string
    {
        return self::should_run_queries($query) ? $join . self::post_join_clause() : $join;
    }

    public static function where_language(string $where, $query): string
    {
        if (! self::should_run_queries($query)) {
            return $where;
        } $lang = ! empty($query->query['lang']) ? (string) $query->query['lang'] : self::request_language();

        return $where . self::post_where_clause($lang);
    }

    public static function fix_main_query_for_shared_slug(WP_Query $query): void
    {
        if (is_admin() || ! $query->is_main_query() || ! self::polylang_active()) {
            return;
        } $url = self::current_request_url();
        $lang = ! empty($query->query['lang']) ? (string) $query->query['lang'] : self::language_from_url($url);

        if ('' === $lang) {
            return;
        } $pagename = '';

        if (! empty($query->query['pagename'])) {
            $pagename = (string) $query->query['pagename'];
        } elseif (! empty($query->query['name']) && empty($query->query['attachment'])) {
            $pagename = (string) $query->query['name'];
        }

if ('' === $pagename) {
            return;
        } $lang_home = untrailingslashit((string) wp_parse_url(pll_home_url($lang), PHP_URL_PATH));
        $path = untrailingslashit($lang_home . '/' . $pagename);
        $post_id = self::post_id_for_language_path($path, $lang);

        if ($post_id <= 0) {
            return;
        } $post = get_post($post_id);

        if (! $post instanceof WP_Post) {
            return;
        } $query->set('pagename', '');
        $query->set('name', '');

        if ('page' === $post->post_type) {
            $query->set('page_id', $post_id);

            return;
        } $query->set('p', $post_id);
        $query->set('post_type', $post->post_type);
    }

    public static function suppress_canonical_for_shared_slug(): void
    {
        if (is_admin() || ! self::polylang_active()) {
            return;
        } $url = self::current_request_url();
        $lang = self::language_from_url($url);

        if ('' === $lang) {
            return;
        } $path = untrailingslashit((string) wp_parse_url($url, PHP_URL_PATH));

        if (self::post_id_for_language_path($path, $lang) > 0) {
            remove_action('template_redirect', 'redirect_canonical');
        }
    }

    public static function block_shared_slug_redirect($redirect_url, $requested_url)
    {
        if (! self::polylang_active() || empty($redirect_url) || ! is_string($requested_url)) {
            return $redirect_url;
        } $lang = self::language_from_url($requested_url);

        if ('' === $lang) {
            return $redirect_url;
        } $req_path = untrailingslashit((string) wp_parse_url($requested_url, PHP_URL_PATH));
        $red_path = untrailingslashit((string) wp_parse_url($redirect_url, PHP_URL_PATH));

        if ($req_path === $red_path) {
            return false;
        }

if (self::post_id_for_language_path($req_path, $lang) > 0) {
            return false;
        }

return $redirect_url;
    }

    public static function block_polylang_canonical($redirect_url, $language)
    {
        if (empty($redirect_url) || ! self::polylang_active()) {
            return $redirect_url;
        } $url = self::current_request_url();
        $lang = is_object($language) && ! empty($language->slug) ? (string) $language->slug : self::language_from_url($url);

        if ('' === $lang) {
            return $redirect_url;
        } $path = untrailingslashit((string) wp_parse_url($url, PHP_URL_PATH));

        if (self::post_id_for_language_path($path, $lang) > 0) {
            return false;
        }

return $redirect_url;
    }

    public static function filter_page_query(string $query): string
    {
        global $wpdb;

        if (! self::should_run_queries()) {
            return $query;
        } $normalized = self::normalize_whitespace($query);

        if (! preg_match("#SELECT ID, post_name, post_parent, post_type FROM {$wpdb->posts} .*#", $normalized)) {
            return $query;
        } $lang = self::request_language();

        if ('' === $lang) {
            return $query;
        } $join_clause = self::post_join_clause();
        $where_clause = self::post_where_clause($lang);

        if ('' === $join_clause || '' === $where_clause) {
            return $query;
        }

if (! preg_match('#(SELECT .* (?=FROM))(FROM .* (?=WHERE))(?:(WHERE .*(?=ORDER))|(WHERE .*$))(.*)#', $normalized, $matches)) {
            return $query;
        } $matches = array_values($matches);

        return $matches[1] . $matches[2] . $join_clause . $matches[3] . $where_clause . $matches[4];
    }

    private static function apply_intended_term_slug(array $data, int $term_id, string $taxonomy, array $args): array
    {
        if (! self::polylang_active() || ! self::is_translated_taxonomy($taxonomy) || empty($data['slug'])) {
            return $data;
        } $intended = self::resolve_intended_term_slug();

        if ('' === $intended || $intended === $data['slug']) {
            return $data;
        } $parent = isset($args['parent']) ? (int) $args['parent'] : 0;

        if (self::should_use_term_slug($intended, $term_id, $taxonomy, $parent)) {
            $data['slug'] = $intended;
        }

return $data;
    }

    private static function resolve_intended_term_slug(): string
    {
        if ('' !== self::$intended_term_slug) {
            return self::$intended_term_slug;
        }

if (isset($_POST['slug'])) {
            $posted = sanitize_title(wp_unslash($_POST['slug']));

            if ('' !== $posted) {
                return $posted;
            }
        }

return '';
    }

    private static function should_use_term_slug(string $slug, int $term_id, string $taxonomy, int $parent): bool
    {
        if ('' === $slug) {
            return false;
        } $lang = self::term_save_language($term_id);

        return '' !== $lang && self::term_slug_unique_in_language($slug, $lang, $taxonomy, $parent, $term_id);
    }

    private static function is_translated_taxonomy(string $taxonomy): bool
    {
        return function_exists('pll_is_translated_taxonomy') && pll_is_translated_taxonomy($taxonomy);
    }

    private static function term_parent_from_object(object $term): int
    {
        return isset($term->parent) ? (int) $term->parent : 0;
    }

    private static function polylang_active(): bool
    {
        return function_exists('pll_current_language');
    }

    private static function should_run_queries($query = null): bool
    {
        if (! self::polylang_active() || ! self::is_frontend_content_request()) {
            return false;
        }

if (self::is_feed_query($query)) {
            return false;
        }

if ($query instanceof WP_Query) {
            $lang = ! empty($query->query['lang']) ? (string) $query->query['lang'] : self::request_language();

            if ('' === $lang) {
                return false;
            }

if (! empty($query->query['post_type']) && function_exists('pll_is_translated_post_type') && ! pll_is_translated_post_type($query->query['post_type'])) {
                return false;
            }

return true;
        }

return '' !== self::request_language();
    }

    private static function is_frontend_content_request(): bool
    {
        if (is_admin() || wp_doing_cron()) {
            return false;
        }

if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) {
            return false;
        }

if (defined('WP_INSTALLING') && WP_INSTALLING) {
            return false;
        }

if (isset($_SERVER['SCRIPT_NAME']) && function_exists('is_login') && is_login()) {
            return false;
        }

return true;
    }

    private static function is_feed_query($query = null): bool
    {
        if ($query instanceof WP_Query) {
            return (bool) $query->is_feed || '' !== (string) $query->get('feed');
        } global $wp_query, $wp;

        if (isset($wp_query) && $wp_query instanceof WP_Query && $wp_query->is_feed) {
            return true;
        }

if (isset($wp) && $wp instanceof WP && ! empty($wp->query_vars['feed'])) {
            return true;
        }

return false;
    }

    private static function request_language(): string
    {
        if (function_exists('pll_current_language')) {
            $lang = (string) pll_current_language('slug');

            if ('' !== $lang) {
                return $lang;
            }
        }

try {
            if (function_exists('PLL') && isset(PLL()->curlang) && is_object(PLL()->curlang) && ! empty(PLL()->curlang->slug)) {
                return (string) PLL()->curlang->slug;
            }
        } catch (\Throwable $e) {
        }

return '';
    }

    private static function post_id_for_language_path(string $path, string $lang): int
    {
        global $wpdb;

        if ('' === $path || '' === $lang || ! function_exists('pll_home_url')) {
            return 0;
        } $cache_key = $lang . '|' . $path;

        if (isset(self::$path_post_cache[ $cache_key ])) {
            return self::$path_post_cache[ $cache_key ];
        } $pagename = self::pagename_from_language_path($path, $lang);

        if ('' === $pagename) {
            self::$path_post_cache[ $cache_key ] = 0;

            return 0;
        }

if (str_contains($pagename, '/')) {
            $page = get_page_by_path($pagename, OBJECT, 'page');

            if ($page instanceof WP_Post && function_exists('pll_get_post_language')) {
                $result = pll_get_post_language($page->ID, 'slug') === $lang ? (int) $page->ID : 0;
                self::$path_post_cache[ $cache_key ] = $result;

                return $result;
            } self::$path_post_cache[ $cache_key ] = 0;

            return 0;
        } $join = self::post_join_clause();
        $where = self::post_where_clause($lang);

        if ('' === $join || '' === $where) {
            self::$path_post_cache[ $cache_key ] = 0;

            return 0;
        } $types = self::translated_post_types();
        $placeholders = implode(', ', array_fill(0, count($types), '%s'));
        $sql = "SELECT ID FROM {$wpdb->posts} {$join} WHERE post_name = %s AND post_type IN ({$placeholders}) AND post_status = 'publish' {$where} LIMIT 1";
        $prepare = array_merge(array($pagename), $types);
        $result = (int) $wpdb->get_var($wpdb->prepare($sql, $prepare));
        self::$path_post_cache[ $cache_key ] = $result;

        return $result;
    }

    private static function pagename_from_language_path(string $path, string $lang): string
    {
        $lang_home_path = untrailingslashit((string) wp_parse_url(pll_home_url($lang), PHP_URL_PATH));
        $site_home_path = untrailingslashit((string) wp_parse_url(home_url('/'), PHP_URL_PATH));

        if ('' !== $lang_home_path && $lang_home_path !== $site_home_path) {
            if (0 !== strpos(trailingslashit($path), trailingslashit($lang_home_path))) {
                return '';
            }

return trim(substr($path, strlen($lang_home_path)), '/');
        }

if ('' !== $site_home_path && 0 === strpos($path, $site_home_path)) {
            return trim(substr($path, strlen($site_home_path)), '/');
        }

return trim($path, '/');
    }

    private static function language_from_url(string $url): string
    {
        if (! function_exists('PLL') || ! isset(PLL()->model) || ! is_callable(array(PLL()->model, 'get_languages_list'))) {
            return self::request_language();
        } $path = untrailingslashit((string) wp_parse_url($url, PHP_URL_PATH));
        $default = function_exists('pll_default_language') ? (string) pll_default_language('slug') : '';

        try {
            foreach (PLL()->model->get_languages_list() as $language) {
                if (! is_object($language) || empty($language->slug) || ! function_exists('pll_home_url')) {
                    continue;
                }

if ((string) $language->slug === $default) {
                    continue;
                } $lang_path = untrailingslashit((string) wp_parse_url(pll_home_url($language->slug), PHP_URL_PATH));

                if ('' === $lang_path) {
                    continue;
                }

if ($path === $lang_path || 0 === strpos(trailingslashit($path), trailingslashit($lang_path))) {
                    return (string) $language->slug;
                }
            }
        } catch (\Throwable $e) {
            return self::request_language();
        }

return '' !== $default ? $default : self::request_language();
    }

    private static function current_request_url(): string
    {
        if (empty($_SERVER['HTTP_HOST'])) {
            return home_url('/');
        } $scheme = is_ssl() ? 'https://' : 'http://';
        $host = sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST']));
        $uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '/';

        return $scheme . $host . $uri;
    }

    private static function translated_post_types(): array
    {
        $types = array('page', 'post');

        if (class_exists('PufferGo_Polylang')) {
            $types = array_merge($types, PufferGo_Polylang::TRANSLATABLE_POST_TYPES);
        }

return array_values(array_unique($types));
    }

    private static function post_save_language(int $post_id): string
    {
        if ('' !== self::$scoped_lang) {
            return self::$scoped_lang;
        }

if ($post_id > 0 && function_exists('pll_get_post_language')) {
            $lang = (string) pll_get_post_language($post_id, 'slug');

            if ('' !== $lang) {
                return $lang;
            }
        }

if (isset($_REQUEST['lang'])) {
            return sanitize_key(wp_unslash($_REQUEST['lang']));
        }

return (string) pll_current_language('slug');
    }

    private static function term_save_language(int $term_id): string
    {
        if ('' !== self::$scoped_lang) {
            return self::$scoped_lang;
        }

if ($term_id > 0 && function_exists('pll_get_term_language')) {
            $lang = (string) pll_get_term_language($term_id, 'slug');

            if ('' !== $lang) {
                return $lang;
            }
        }

if (isset($_POST['tag_ID'])) {
            $edit_id = absint(wp_unslash($_POST['tag_ID']));

            if ($edit_id > 0 && function_exists('pll_get_term_language')) {
                $lang = (string) pll_get_term_language($edit_id, 'slug');

                if ('' !== $lang) {
                    return $lang;
                }
            }
        }

if (isset($_POST['term_lang_choice'])) {
            return sanitize_key(wp_unslash($_POST['term_lang_choice']));
        }

if (isset($_REQUEST['lang'])) {
            return sanitize_key(wp_unslash($_REQUEST['lang']));
        }

return (string) pll_current_language('slug');
    }

    private static function term_slug_unique_in_language(string $slug, string $lang, string $taxonomy, int $parent, int $exclude_term_id): bool
    {
        try {
            if (! function_exists('PLL') || ! isset(PLL()->model) || ! is_callable(array(PLL()->model, 'term_exists_by_slug'))) {
                return false;
            } $language = is_callable(array(PLL()->model, 'get_language')) ? PLL()->model->get_language($lang) : null;

            if (! is_object($language)) {
                return false;
            } $existing = (int) PLL()->model->term_exists_by_slug($slug, $language, $taxonomy, $parent);

            return ! $existing || ($exclude_term_id > 0 && $existing === $exclude_term_id);
        } catch (\Throwable $e) {
            return false;
        }
    }

    private static function post_join_clause(): string
    {
        try {
            if (function_exists('PLL') && isset(PLL()->model->post) && is_callable(array(PLL()->model->post, 'join_clause'))) {
                return (string) PLL()->model->post->join_clause();
            } global $polylang;

            if (isset($polylang->model) && is_callable(array($polylang->model, 'join_clause'))) {
                return (string) $polylang->model->join_clause('post');
            }
        } catch (\Throwable $e) {
        }

return '';
    }

    private static function post_where_clause(string $lang): string
    {
        try {
            if (function_exists('PLL') && isset(PLL()->model->post) && is_callable(array(PLL()->model->post, 'where_clause'))) {
                return (string) PLL()->model->post->where_clause($lang);
            } global $polylang;

            if (isset($polylang->model) && is_callable(array($polylang->model, 'where_clause'))) {
                return (string) $polylang->model->where_clause($lang, 'post');
            }
        } catch (\Throwable $e) {
        }

return '';
    }

    private static function normalize_whitespace(string $sql): string
    {
        return trim((string) preg_replace('/\s+/', ' ', $sql));
    }
}
