<?php
if (!defined('ABSPATH')) exit;

class RANKFORGE_Competitor_Coexistence {
    private static $instance = null;
    public static function instance() { if (!self::$instance) self::$instance = new self(); return self::$instance; }

    private function __construct() {
        add_filter('aioseo_disable', [$this, 'disable_aioseo_frontend'], 20);
        add_filter('wpseo_frontend_presenters', [$this, 'filter_yoast_presenters'], 20, 2);
        add_action('wp', [$this, 'schedule_late_head_suppression'], 999);
    }

    /**
 * Whether Rank Forge should suppress the output of other active SEO plugins
 * (Yoast, Rank Math, AIOSEO, SEOPress).
 *
 * the Settings → General toggle was removed in favor of "works out
 * of the box". Suppression is now always-on at the code level — running two
 * SEO plugins that both emit meta/OG/canonical is an antipattern that
 * produces duplicate tags. Any previously-stored `false` in
 * `rankforge_suppress_competitors` is intentionally ignored. (If support
 * ever needs an escape hatch for a third-party schema plugin, reintroduce a
 * hidden Advanced toggle per the ticket's note.)
 *
 * @return bool
 */
    public static function is_suppression_enabled() {
        return true;
    }

    public function disable_aioseo_frontend($disabled) {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) return $disabled;
        if (!self::is_suppression_enabled()) return $disabled;
        return true;
    }

    public function filter_yoast_presenters($presenters, $context) {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron() || !is_array($presenters)) {
            return $presenters;
        }

        if (!self::is_suppression_enabled()) {
            return $presenters;
        }

        return array_values(array_filter($presenters, function($presenter) {
            $class = is_object($presenter) ? get_class($presenter) : '';
            if ($class === '') {
                return true;
            }

            if ($class === 'Yoast\\WP\\SEO\\Presenters\\Canonical_Presenter' && $this->has_rankforge_custom_canonical()) {
                return false;
            }

            $blocked_exact = [
                'Yoast\\WP\\SEO\\Presenters\\Meta_Description_Presenter',
                'Yoast\\WP\\SEO\\Presenters\\Robots_Presenter',
            ];
            if (in_array($class, $blocked_exact, true)) {
                return false;
            }

            foreach ([
                'Yoast\\WP\\SEO\\Presenters\\Open_Graph\\',
                'Yoast\\WP\\SEO\\Presenters\\Twitter\\',
            ] as $blocked_prefix) {
                if (strpos($class, $blocked_prefix) === 0) {
                    return false;
                }
            }

            return true;
        }));
    }

    public function schedule_late_head_suppression() {
        if (is_admin() || wp_doing_ajax() || wp_doing_cron()) return;
        if (!self::is_suppression_enabled()) return;
        add_action('wp_head', [$this, 'suppress_seopress_head_output'], 0);
        add_action('wp_head', [$this, 'ensure_wp_robots_output'], 0);
    }

    public function suppress_seopress_head_output() {
        $has_custom_canonical = $this->has_rankforge_custom_canonical();

        $this->remove_wp_head_callbacks(function($callback) {
            $name = $this->callback_name($callback);
            if (strpos($name, 'SEOPress\\Actions\\Front\\Metas\\DescriptionMeta::') === 0) return true;
            if (strpos($name, 'seopress_social_') === 0) return true;
            return in_array($name, [
                'seopress_titles_the_description',
                'seopress_titles_advanced_robots_hook',
                'seopress_social_website_option',
                'seopress_advanced_advanced_google_hook',
                'seopress_advanced_advanced_bing_hook',
                'seopress_advanced_advanced_pinterest_hook',
                'seopress_advanced_advanced_yandex_hook',
                'seopress_advanced_advanced_baidu_hook',
                'seopress_advanced_advanced_facebook_hook',
                'seopress_advanced_advanced_seznam_hook',
            ], true);
        });

        if ($has_custom_canonical) {
            $this->remove_wp_head_callbacks(function($callback) {
                return in_array($this->callback_name($callback), [
                    'seopress_titles_canonical_post_hook',
                    'seopress_titles_canonical_term_hook',
                    'seopress_titles_canonical_hook',
                ], true);
            });
        }

        remove_filter('wp_robots', 'seopress_robots_wc_pages', 20);
    }

    public function ensure_wp_robots_output() {
        if (class_exists('RANKFORGE_Robots_Meta')) {
            $robots_meta = RANKFORGE_Robots_Meta::instance();
            if (!has_filter('wp_robots', [$robots_meta, 'filter_wp_robots'])) {
                add_filter('wp_robots', [$robots_meta, 'filter_wp_robots']);
            }
        }

        if (function_exists('wp_robots') && !has_action('wp_head', 'wp_robots')) {
            add_action('wp_head', 'wp_robots', 1);
        }

        if ($this->has_rankforge_custom_canonical()) {
            $this->remove_rankmath_canonical_output();
        }
    }

    private function has_rankforge_custom_canonical() {
        return class_exists('RANKFORGE_Meta_Generator')
            && is_singular()
            && RANKFORGE_Meta_Generator::get_custom_canonical_url(get_queried_object_id()) !== '';
    }

    private function remove_rankmath_canonical_output() {
        $this->remove_callbacks_from_hook('rank_math/head', function($callback) {
            return $this->callback_name($callback) === 'RankMath\\Frontend\\Head::canonical';
        });
    }

    private function remove_wp_head_callbacks($should_remove) {
        $this->remove_callbacks_from_hook('wp_head', $should_remove);
    }

    private function remove_callbacks_from_hook($hook, $should_remove) {
        global $wp_filter;
        if (empty($wp_filter[$hook]) || !isset($wp_filter[$hook]->callbacks)) return;

        foreach ($wp_filter[$hook]->callbacks as $priority => $callbacks) {
            foreach ($callbacks as $data) {
                $callback = $data['function'] ?? null;
                if (!$callback || !$should_remove($callback)) continue;
                remove_action($hook, $callback, (int) $priority);
            }
        }
    }

    private function callback_name($callback) {
        if (is_string($callback)) return $callback;
        if (is_array($callback) && isset($callback[0], $callback[1])) {
            $owner = is_object($callback[0]) ? get_class($callback[0]) : (string) $callback[0];
            return $owner . '::' . $callback[1];
        }
        return '';
    }
}
