<?php

namespace WPSN\Modules\HeadCleanup;

use WPSN\Core\AbstractModule;

defined('ABSPATH') || exit;

final class HeadCleanupModule extends AbstractModule
{
    /**
     * @var array<string,bool>
     */
    private array $runtimeConfig = [];

    private bool $outputBufferStarted = false;

    public function id(): string
    {
        return 'head_cleanup';
    }

    public function name(): string
    {
        return __('Head Cleanup', 'wp-seo-next');
    }

    public function description(): string
    {
        return __('Opt-in cleanup of default WordPress head tags, discovery links, feed endpoints, pingback output, and related front-end noise.', 'wp-seo-next');
    }

    public function defaultEnabled(): bool
    {
        // Safe by default because nothing changes until the feature is explicitly enabled.
        return true;
    }

    public function register(): void
    {
        add_action('init', [$this, 'apply'], 20);
    }

    public function apply(): void
    {
        if ($this->shouldSkipRequest()) {
            return;
        }

        $config = $this->settings->get('head_cleanup', []);
        if (!is_array($config)) {
            $config = [];
        }

        $config = HeadCleanupProfile::sanitize($config);
        if (empty($config['enabled'])) {
            return;
        }

        $applied = [];

        if (!empty($config['remove_emoji'])) {
            remove_action('wp_head', 'print_emoji_detection_script', 7);
            remove_action('wp_print_styles', 'print_emoji_styles');
            $applied[] = 'remove_emoji';
        }

        if (!empty($config['remove_wlwmanifest'])) {
            remove_action('wp_head', 'wlwmanifest_link');
            $applied[] = 'remove_wlwmanifest';
        }

        if (!empty($config['remove_rsd'])) {
            remove_action('wp_head', 'rsd_link');
            $applied[] = 'remove_rsd';
        }

        if (!empty($config['remove_generator'])) {
            remove_action('wp_head', 'wp_generator');
            $applied[] = 'remove_generator';
        }

        if (!empty($config['remove_shortlink'])) {
            remove_action('wp_head', 'wp_shortlink_wp_head', 10);
            remove_action('template_redirect', 'wp_shortlink_header', 11);
            $applied[] = 'remove_shortlink';
        }

        if (!empty($config['remove_rest_links'])) {
            remove_action('wp_head', 'rest_output_link_wp_head', 10);
            remove_action('template_redirect', 'rest_output_link_header', 11);
            $applied[] = 'remove_rest_links';
        }

        if (!empty($config['disable_oembed_discovery'])) {
            remove_action('wp_head', 'wp_oembed_add_discovery_links', 10);
            remove_action('wp_head', 'wp_oembed_add_host_js');
            add_filter('embed_oembed_discover', '__return_false');
            $applied[] = 'disable_oembed_discovery';
        }

        if (!empty($config['remove_feed_links'])) {
            remove_action('wp_head', 'feed_links', 2);
            remove_action('wp_head', 'feed_links_extra', 3);
            $applied[] = 'remove_feed_links';
        }

        $this->runtimeConfig = [
            'remove_pingback_link' => !empty($config['remove_pingback_link']),
            'disable_feed_endpoints_global' => !empty($config['disable_feed_endpoints_global']),
            'disable_feed_endpoints_search' => !empty($config['disable_feed_endpoints_search']),
            'disable_feed_endpoints_comments' => !empty($config['disable_feed_endpoints_comments']),
            'remove_pingback_header' => !empty($config['remove_pingback_header']),
            'remove_powered_by_header' => !empty($config['remove_powered_by_header']),
        ];

        if (!empty($this->runtimeConfig['remove_pingback_link'])) {
            add_action('template_redirect', [$this, 'startOutputBuffer'], 0);
            $applied[] = 'remove_pingback_link';
        }

        if (
            !empty($this->runtimeConfig['disable_feed_endpoints_global'])
            || !empty($this->runtimeConfig['disable_feed_endpoints_search'])
            || !empty($this->runtimeConfig['disable_feed_endpoints_comments'])
        ) {
            add_action('template_redirect', [$this, 'maybeDisableFeedRequest'], 0);
            if (!empty($this->runtimeConfig['disable_feed_endpoints_global'])) {
                $applied[] = 'disable_feed_endpoints_global';
            }
            if (!empty($this->runtimeConfig['disable_feed_endpoints_search'])) {
                $applied[] = 'disable_feed_endpoints_search';
            }
            if (!empty($this->runtimeConfig['disable_feed_endpoints_comments'])) {
                $applied[] = 'disable_feed_endpoints_comments';
            }
        }

        if (!empty($this->runtimeConfig['remove_pingback_header']) || !empty($this->runtimeConfig['remove_powered_by_header'])) {
            add_filter('wp_headers', [$this, 'filterHeaders'], 99);
            add_action('send_headers', [$this, 'removeHeadersAtSendTime'], 99);
            if (!empty($this->runtimeConfig['remove_pingback_header'])) {
                $applied[] = 'remove_pingback_header';
            }
            if (!empty($this->runtimeConfig['remove_powered_by_header'])) {
                $applied[] = 'remove_powered_by_header';
            }
        }

        if (!empty($applied)) {
            /**
             * Fires after head cleanup rules are applied for the current request.
             *
             * @param string[] $applied
             * @param array<string, mixed> $config
             * @param string[] $labels
             */
            do_action('wpsn/head_cleanup/applied', $applied, $config, HeadCleanupProfile::activeItemLabels($config));
        }
    }

    public function startOutputBuffer(): void
    {
        if (empty($this->runtimeConfig['remove_pingback_link']) || $this->outputBufferStarted) {
            return;
        }

        if ($this->isFeedRequest()) {
            return;
        }

        $this->outputBufferStarted = true;
        ob_start([$this, 'filterHtmlOutput']);
    }

    public function maybeDisableFeedRequest(): void
    {
        if (!$this->shouldDisableCurrentFeedRequest()) {
            return;
        }

        global $wp_query;

        if ($wp_query instanceof \WP_Query) {
            $wp_query->set_404();
        }

        status_header(404);
        nocache_headers();

        if (function_exists('get_404_template')) {
            $template = get_404_template();
            if (is_string($template) && $template !== '') {
                include $template;
                exit;
            }
        }

        exit;
    }

    public function filterHtmlOutput(string $html): string
    {
        if ($html === '' || empty($this->runtimeConfig['remove_pingback_link'])) {
            return $html;
        }

        return (string) preg_replace('~<link\b(?=[^>]*\brel=("|\')pingback\1)[^>]*>\s*~i', '', $html);
    }

    private function shouldSkipRequest(): bool
    {
        if (is_admin()) {
            return true;
        }

        if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
            return true;
        }

        if (defined('REST_REQUEST') && REST_REQUEST) {
            return true;
        }

        return false;
    }

    private function isFeedRequest(): bool
    {
        return function_exists('is_feed') && is_feed();
    }

    private function shouldDisableCurrentFeedRequest(): bool
    {
        if (!$this->isFeedRequest()) {
            return false;
        }

        if (!empty($this->runtimeConfig['disable_feed_endpoints_global'])) {
            return true;
        }

        if (!empty($this->runtimeConfig['disable_feed_endpoints_comments']) && function_exists('is_comment_feed') && is_comment_feed()) {
            return true;
        }

        if (!empty($this->runtimeConfig['disable_feed_endpoints_search']) && function_exists('is_search') && is_search()) {
            return true;
        }

        return false;
    }

    /**
     * @param array<string,string> $headers
     * @return array<string,string>
     */
    public function filterHeaders(array $headers): array
    {
        if (empty($this->runtimeConfig)) {
            return $headers;
        }

        if (!empty($this->runtimeConfig['remove_pingback_header'])) {
            $headers = $this->unsetHeaderCaseInsensitive($headers, 'X-Pingback');
            $headers = $this->unsetHeaderCaseInsensitive($headers, 'Pingback');
        }

        if (!empty($this->runtimeConfig['remove_powered_by_header'])) {
            $headers = $this->unsetHeaderCaseInsensitive($headers, 'X-Powered-By');
        }

        return $headers;
    }

    public function removeHeadersAtSendTime(): void
    {
        if (!function_exists('header_remove') || empty($this->runtimeConfig)) {
            return;
        }

        if (!empty($this->runtimeConfig['remove_pingback_header'])) {
            @header_remove('X-Pingback');
            @header_remove('Pingback');
        }

        if (!empty($this->runtimeConfig['remove_powered_by_header'])) {
            @header_remove('X-Powered-By');
        }
    }

    /**
     * @param array<string,string> $headers
     * @return array<string,string>
     */
    private function unsetHeaderCaseInsensitive(array $headers, string $target): array
    {
        $target = strtolower($target);
        foreach (array_keys($headers) as $key) {
            if (strtolower((string) $key) === $target) {
                unset($headers[$key]);
            }
        }

        return $headers;
    }
}
