<?php

namespace WPSN\Modules\Diagnostics\Admin;

use WPSN\Core\AjaxMode;
use WPSN\Core\Capabilities;
use WPSN\Core\Container;

defined('ABSPATH') || exit;

/**
 * AJAX Inventory
 *
 * Support/ops page to understand where WP SEO Next uses admin-ajax.php.
 *
 * - Lists AJAX-heavy admin scripts (not loaded when AJAX is disabled globally)
 * - Lists registered wp_ajax_* hooks for actions starting with wpsn_
 */
final class AjaxInventoryAdmin
{
    public const PAGE_SLUG = 'wpsn-ajax-inventory';

    private Container $container;

    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    public function register(): void
    {
        add_action('admin_menu', [$this, 'addMenu']);
    }

    public function addMenu(): void
    {
        add_management_page(
            __('AJAX Inventory', 'wp-seo-next'),
            __('AJAX Inventory', 'wp-seo-next'),
            Capabilities::CAP_TOOLS,
            self::PAGE_SLUG,
            [$this, 'render']
        );
    }

    public function render(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_die(esc_html__('You do not have permission to access this page.', 'wp-seo-next'));
        }

        $settings = $this->container->get('settings');
        $ajaxEnabled = AjaxMode::isEnabled($settings);

        $scripts = $this->scriptInventory();
        $hooks = $this->ajaxHooksInventory();

        $nPriv = 0;
        $nNoPriv = 0;
        foreach ($hooks as $h) {
            if (($h['kind'] ?? '') === 'nopriv') {
                $nNoPriv++;
            } else {
                $nPriv++;
            }
        }

        echo '<div class="wrap">';
        echo '<h1>' . esc_html__('AJAX Inventory', 'wp-seo-next') . '</h1>';

        echo '<p class="description">' . esc_html__('This page lists WP SEO Next features that rely on admin-ajax.php (live mode) and the registered wp_ajax_* hooks. On some shared hosting environments admin-ajax can trigger HTTP 503, so WP SEO Next supports a global "AJAX live mode" switch.', 'wp-seo-next') . '</p>';

        if ($ajaxEnabled) {
            echo '<div class="notice notice-success inline"><p>' . esc_html__('AJAX live mode is currently ENABLED.', 'wp-seo-next') . '</p></div>';
        } else {
            echo '<div class="notice notice-warning inline"><p>' . esc_html__('AJAX live mode is currently DISABLED. WP SEO Next will not load AJAX-heavy admin scripts and will reject wpsn_* admin-ajax requests.', 'wp-seo-next') . '</p></div>';
        }

        echo '<p>';
        echo '<a class="button" href="' . esc_url(admin_url('tools.php?page=wpsn-diagnostics')) . '">' . esc_html__('Open Diagnostics → Modes', 'wp-seo-next') . '</a>';
        echo '</p>';

        echo '<h2>' . esc_html__('AJAX-heavy admin scripts', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . esc_html__('These scripts contain admin-ajax calls. When AJAX live mode is disabled, WP SEO Next will dequeue/deregister them.', 'wp-seo-next') . '</p>';

        echo '<table class="widefat striped" style="max-width: 1200px;">';
        echo '<thead><tr>';
        echo '<th>' . esc_html__('Handle', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Asset', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Feature', 'wp-seo-next') . '</th>';
        echo '<th style="width:120px;">' . esc_html__('Allowed', 'wp-seo-next') . '</th>';
        echo '</tr></thead><tbody>';

        foreach ($scripts as $row) {
            $allowed = $ajaxEnabled ? __('Yes', 'wp-seo-next') : __('No (blocked)', 'wp-seo-next');
            echo '<tr>';
            echo '<td><code>' . esc_html((string) ($row['handle'] ?? '')) . '</code></td>';
            echo '<td><code>' . esc_html((string) ($row['asset'] ?? '')) . '</code></td>';
            echo '<td>' . esc_html((string) ($row['feature'] ?? '')) . '</td>';
            echo '<td>' . esc_html($allowed) . '</td>';
            echo '</tr>';
        }

        echo '</tbody></table>';

        echo '<h2 style="margin-top:24px;">' . esc_html__('Registered wp_ajax hooks (wpsn_*)', 'wp-seo-next') . '</h2>';
        echo '<p class="description">' . sprintf(
            /* translators: 1: number of privileged hooks, 2: number of nopriv hooks */
            esc_html__('Detected: %1$d privileged (wp_ajax_*) hooks, %2$d non-privileged (wp_ajax_nopriv_*) hooks.', 'wp-seo-next'),
            (int) $nPriv,
            (int) $nNoPriv
        ) . '</p>';

        echo '<table class="widefat striped" style="max-width: 1200px;">';
        echo '<thead><tr>';
        echo '<th>' . esc_html__('Hook', 'wp-seo-next') . '</th>';
        echo '<th style="width:80px;">' . esc_html__('#', 'wp-seo-next') . '</th>';
        echo '<th>' . esc_html__('Example callbacks', 'wp-seo-next') . '</th>';
        echo '<th style="width:120px;">' . esc_html__('Blocked', 'wp-seo-next') . '</th>';
        echo '</tr></thead><tbody>';

        foreach ($hooks as $row) {
            $hook = (string) ($row['hook'] ?? '');
            $count = (int) ($row['count'] ?? 0);
            $examples = (array) ($row['examples'] ?? []);

            $blocked = (!$ajaxEnabled && $hook !== '') ? __('Yes', 'wp-seo-next') : __('No', 'wp-seo-next');

            echo '<tr>';
            echo '<td><code>' . esc_html($hook) . '</code></td>';
            echo '<td>' . esc_html((string) $count) . '</td>';
            echo '<td>';
            if (!empty($examples)) {
                echo '<code>' . esc_html(implode(', ', array_slice($examples, 0, 2))) . '</code>';
            } else {
                echo '<span class="description">' . esc_html__('(none)', 'wp-seo-next') . '</span>';
            }
            echo '</td>';
            echo '<td>' . esc_html($blocked) . '</td>';
            echo '</tr>';
        }

        echo '</tbody></table>';

        echo '</div>';
    }

    /**
     * @return array<int,array{handle:string,asset:string,feature:string}>
     */
    private function scriptInventory(): array
    {
        $map = [
            'wpsn-audit' => ['asset' => 'assets/admin/audit.js', 'feature' => 'Audit'],
            'wpsn-bulk-admin' => ['asset' => 'assets/admin/bulk.js', 'feature' => 'Bulk Optimize'],
            'wpsn-bulk-table-import' => ['asset' => 'assets/admin/bulk-table-import.js', 'feature' => 'Bulk Table Import'],
            'wpsn-custom-fields-token-helper' => ['asset' => 'assets/admin/custom-fields-token-helper.js', 'feature' => 'Settings: Custom Field Tokens helper'],
            'wpsn-drift-monitor' => ['asset' => 'assets/admin/drift-monitor.js', 'feature' => 'Drift Monitor (duplicates live mode)'],
            'wpsn-gsc-admin' => ['asset' => 'assets/admin/gsc.js', 'feature' => 'Search Console Hub'],
            'wpsn-media-admin' => ['asset' => 'assets/admin/media.js', 'feature' => 'SEO Media'],
            'wpsn-network-profiles' => ['asset' => 'assets/admin/network-profiles.js', 'feature' => 'Multisite Network Profiles rollout'],
            'wpsn-404-suggest' => ['asset' => 'assets/admin/redirect-404-suggest.js', 'feature' => '404 Redirect Suggestions'],
            'wpsn-schema-validator-admin' => ['asset' => 'assets/admin/schema-validator.js', 'feature' => 'Schema Validator'],
            'wpsn-serp-gap-admin' => ['asset' => 'assets/admin/serp-gap.js', 'feature' => 'SERP Gap Analyzer / Serper helpers'],
            'wpsn-template-tokens-admin' => ['asset' => 'assets/admin/template-tokens.js', 'feature' => 'Template Tokens tester'],
        ];

        $out = [];
        foreach ($map as $handle => $info) {
            $out[] = [
                'handle' => (string) $handle,
                'asset' => (string) ($info['asset'] ?? ''),
                'feature' => (string) ($info['feature'] ?? ''),
            ];
        }

        // Keep in sync with AjaxMode::ajaxScriptHandles().
        return $out;
    }

    /**
     * @return array<int,array{hook:string,kind:string,count:int,examples:array<int,string>}>
     */
    private function ajaxHooksInventory(): array
    {
        global $wp_filter;

        $out = [];

        if (!is_array($wp_filter)) {
            return $out;
        }

        foreach ($wp_filter as $hook => $hookObj) {
            if (!is_string($hook)) {
                continue;
            }

            $kind = '';
            if (strpos($hook, 'wp_ajax_wpsn_') === 0) {
                $kind = 'priv';
            } elseif (strpos($hook, 'wp_ajax_nopriv_wpsn_') === 0) {
                $kind = 'nopriv';
            } else {
                continue;
            }

            $callbacks = [];
            if (is_object($hookObj) && property_exists($hookObj, 'callbacks')) {
                $callbacks = is_array($hookObj->callbacks) ? $hookObj->callbacks : [];
            } elseif (is_array($hookObj)) {
                // Old style.
                $callbacks = $hookObj;
            }

            $examples = [];
            $count = 0;
            foreach ($callbacks as $prio => $list) {
                if (!is_array($list)) {
                    continue;
                }
                foreach ($list as $cb) {
                    $count++;
                    if (isset($cb['function'])) {
                        $examples[] = $this->formatCallback($cb['function']);
                    }
                }
            }

            $examples = array_values(array_unique(array_filter($examples)));

            $out[] = [
                'hook' => $hook,
                'kind' => $kind,
                'count' => $count,
                'examples' => $examples,
            ];
        }

        usort($out, static function (array $a, array $b): int {
            return strcmp((string) ($a['hook'] ?? ''), (string) ($b['hook'] ?? ''));
        });

        return $out;
    }

    /**
     * @param mixed $fn
     */
    private function formatCallback($fn): string
    {
        if (is_string($fn)) {
            return $fn;
        }
        if (is_array($fn) && count($fn) === 2) {
            $obj = $fn[0];
            $method = is_string($fn[1]) ? $fn[1] : '';
            if (is_object($obj)) {
                return get_class($obj) . '::' . $method;
            }
            if (is_string($obj)) {
                return $obj . '::' . $method;
            }
        }
        if ($fn instanceof \Closure) {
            return 'Closure';
        }
        return 'Unknown';
    }
}
