<?php

namespace WPSN\Admin;

use WPSN\Core\Capabilities;
use WPSN\Core\AjaxMode;
use WPSN\Core\Container;
use WPSN\Domain\SeoDocumentContext;
use WPSN\SEO\TemplateEngine;

defined('ABSPATH') || exit;

/**
 * Tools → Tokens & Variables
 *
 * A small UX helper for TemplateEngine templates:
 * - cheat sheet for supported tokens
 * - advanced syntax docs (fallback + transforms)
 * - template tester (AJAX)
 */
final class TokensVariablesAdmin
{
    private const AJAX_NONCE_ACTION = 'wpsn_template_test';
    private const POST_NONCE_ACTION = 'wpsn_template_test_post';

    private Container $container;

    private string $hookSuffix = '';

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

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

    public function addMenu(): void
    {
        $this->hookSuffix = (string) add_management_page(
            __('Tokens & Variables', 'wp-seo-next'),
            __('Tokens & Variables', 'wp-seo-next'),
            Capabilities::CAP_TOOLS,
            'wpsn-tokens-variables',
            [$this, 'render']
        );
    }

    public function enqueueAssets(string $hook): void
    {
        if ($this->hookSuffix !== '' && $hook !== $this->hookSuffix) {
            return;
        }
        if ($this->hookSuffix === '' && (!isset($_GET['page']) || (string) wp_unslash($_GET['page']) !== 'wpsn-tokens-variables')) {
            return;
        }

        wp_enqueue_script(
            'wpsn-template-tokens-admin',
            defined('WPSN_PLUGIN_URL') ? (WPSN_PLUGIN_URL . 'assets/admin/template-tokens.js') : plugins_url('assets/admin/template-tokens.js', defined('WPSN_PLUGIN_FILE') ? WPSN_PLUGIN_FILE : __FILE__),
            ['jquery'],
            defined('WPSN_VERSION') ? WPSN_VERSION : '0.0.0',
            true
        );

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

        wp_localize_script('wpsn-template-tokens-admin', 'WPSNTemplateTokens', [
            'ajaxEnabled' => $ajaxEnabled,
            'ajaxUrl' => $ajaxEnabled ? admin_url('admin-ajax.php') : '',
            'nonce' => $ajaxEnabled ? wp_create_nonce(self::AJAX_NONCE_ACTION) : '',
            'i18n' => [
                'rendering' => __('Rendering…', 'wp-seo-next'),
                'emptyTemplate' => __('Please enter a template.', 'wp-seo-next'),
                'needPostId' => __('Please enter a valid Post ID.', 'wp-seo-next'),
                'needTermId' => __('Please enter a valid Term ID.', 'wp-seo-next'),
                'needTax' => __('Please enter a taxonomy (e.g. category).', 'wp-seo-next'),
                'needAuthorId' => __('Please enter a valid Author ID.', 'wp-seo-next'),
                'error' => __('Error:', 'wp-seo-next'),
            ],
        ]);
    }

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

        // Non-AJAX tester (busy hosts / HTTP 503 hardening).
        $submitted = !empty($_POST['wpsn_tt_submit']);
        $error = '';
        $output = '';
        $debugText = '';

        $type = isset($_POST['object_type']) ? sanitize_key((string) wp_unslash($_POST['object_type'])) : 'post';
        if (!in_array($type, ['post', 'term', 'author', 'home'], true)) {
            $type = 'post';
        }

        $templateValue = isset($_POST['template']) ? (string) wp_unslash($_POST['template']) : '%title% %sep% %sitename%';
        $templateValue = trim($templateValue);

        $postIdValue = isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0;
        $termIdValue = isset($_POST['term_id']) ? (int) $_POST['term_id'] : 0;
        $taxValue = isset($_POST['taxonomy']) ? sanitize_key((string) wp_unslash($_POST['taxonomy'])) : 'category';
        $authorIdValue = isset($_POST['author_id']) ? (int) $_POST['author_id'] : 0;

        if ($submitted) {
            check_admin_referer(self::POST_NONCE_ACTION);

            if ($templateValue === '') {
                $error = __('Please enter a template.', 'wp-seo-next');
            }

            // Safety: avoid extremely large payloads.
            if ($templateValue !== '') {
                if (function_exists('mb_strlen') && mb_strlen($templateValue) > 4000) {
                    $templateValue = (string) mb_substr($templateValue, 0, 4000);
                } elseif (strlen($templateValue) > 4000) {
                    $templateValue = substr($templateValue, 0, 4000);
                }
            }

            if ($error === '') {
                $ctx = null;
                if ($type === 'home') {
                    $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_HOME, null, null, home_url('/'), 1);
                } elseif ($type === 'post') {
                    if ($postIdValue <= 0) {
                        $error = __('Please enter a valid Post ID.', 'wp-seo-next');
                    } else {
                        $post = get_post($postIdValue);
                        if (!$post instanceof \WP_Post) {
                            $error = __('Post not found.', 'wp-seo-next');
                        } else {
                            $url = get_permalink($post);
                            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_SINGULAR, (int) $post->ID, (string) $post->post_type, is_string($url) ? $url : '', 1);
                        }
                    }
                } elseif ($type === 'term') {
                    if ($termIdValue <= 0) {
                        $error = __('Please enter a valid Term ID.', 'wp-seo-next');
                    } elseif ($taxValue === '') {
                        $error = __('Please enter a taxonomy (e.g. category).', 'wp-seo-next');
                    } else {
                        $term = get_term($termIdValue, $taxValue);
                        if (!$term || is_wp_error($term)) {
                            $error = __('Term not found.', 'wp-seo-next');
                        } else {
                            $link = get_term_link($term);
                            $url = (!is_wp_error($link) && is_string($link)) ? $link : '';
                            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_TERM, (int) $termIdValue, (string) $taxValue, $url, 1);
                        }
                    }
                } elseif ($type === 'author') {
                    if ($authorIdValue <= 0) {
                        $error = __('Please enter a valid Author ID.', 'wp-seo-next');
                    } else {
                        $u = get_userdata($authorIdValue);
                        if (!$u) {
                            $error = __('Author not found.', 'wp-seo-next');
                        } else {
                            $url = get_author_posts_url($authorIdValue);
                            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_ARCHIVE, (int) $authorIdValue, 'author', is_string($url) ? $url : '', 1);
                        }
                    }
                }

                if ($error === '' && $ctx instanceof SeoDocumentContext) {
                    /** @var TemplateEngine $engine */
                    $engine = $this->container->get('template_engine');
                    $dbg = $engine->renderWithDebug($templateValue, $ctx);
                    $output = (string) ($dbg['output'] ?? '');
                    $tokens = isset($dbg['tokens']) && is_array($dbg['tokens']) ? $dbg['tokens'] : [];
                    $debugText = $this->formatTokenBreakdown($tokens);
                }
            }
        }

        $baseTokens = [
            '%title%' => __('Context title (post/term/archive/search)', 'wp-seo-next'),
            '%sitename%' => __('Site name', 'wp-seo-next'),
            '%sitedesc%' => __('Site description (tagline)', 'wp-seo-next'),
            '%sep%' => __('Separator from Settings', 'wp-seo-next'),
            '%excerpt%' => __('Excerpt (or derived from content)', 'wp-seo-next'),
            '%page%' => __('Page label for paged views (e.g. “Page 2”)', 'wp-seo-next'),
            '%pagenum%' => __('Page number for paged views', 'wp-seo-next'),
            '%url%' => __('Current URL (context URL)', 'wp-seo-next'),
        ];

        $singularTokens = [
            '%post_id%' => __('Post ID', 'wp-seo-next'),
            '%slug%' => __('Post slug', 'wp-seo-next'),
            '%permalink%' => __('Post permalink', 'wp-seo-next'),
            '%post_type%' => __('Post type (slug)', 'wp-seo-next'),
            '%post_type_singular%' => __('Post type label (singular)', 'wp-seo-next'),
            '%post_type_plural%' => __('Post type label (plural)', 'wp-seo-next'),
            '%date%' => __('Post date (site date format)', 'wp-seo-next'),
            '%year%' => __('Post year (YYYY)', 'wp-seo-next'),
            '%month%' => __('Post month name', 'wp-seo-next'),
            '%monthnum%' => __('Post month number (01-12)', 'wp-seo-next'),
            '%day%' => __('Post day (01-31)', 'wp-seo-next'),
            '%author_name%' => __('Author display name', 'wp-seo-next'),
            '%author_first_name%' => __('Author first name', 'wp-seo-next'),
            '%author_last_name%' => __('Author last name', 'wp-seo-next'),
            '%author_bio%' => __('Author bio/description', 'wp-seo-next'),
        ];

        $termTokens = [
            '%term_id%' => __('Term ID', 'wp-seo-next'),
            '%term_name%' => __('Term name', 'wp-seo-next'),
            '%term_slug%' => __('Term slug', 'wp-seo-next'),
            '%term_description%' => __('Term description', 'wp-seo-next'),
            '%taxonomy%' => __('Taxonomy (slug)', 'wp-seo-next'),
            '%taxonomy_singular%' => __('Taxonomy label (singular)', 'wp-seo-next'),
            '%taxonomy_plural%' => __('Taxonomy label (plural)', 'wp-seo-next'),
            '%permalink%' => __('Term link', 'wp-seo-next'),
        ];

        $dynamicTokens = [
            '%meta:key%' => __('Native postmeta/termmeta (core)', 'wp-seo-next'),
            '%opt:option_name%' => __('WordPress option via get_option()', 'wp-seo-next'),
            '%siteopt:option_name%' => __('Network option via get_site_option() (multisite)', 'wp-seo-next'),
            '%user:meta_key%' => __('User meta for current author (singular/author archive)', 'wp-seo-next'),
            '%acf:field%' => __('ACF field (if Custom Fields module enabled)', 'wp-seo-next'),
            '%acfopt:field%' => __('ACF Options Page field (global) (if Custom Fields module enabled)', 'wp-seo-next'),
            '%mb:field%' => __('Meta Box field (if Custom Fields module enabled)', 'wp-seo-next'),
            '%jet:field%' => __('JetEngine field (incl. Custom Meta Storage) (if Custom Fields module enabled)', 'wp-seo-next'),
            '%pods:field%' => __('Pods field (infers pod from post type/taxonomy) (if Custom Fields module enabled)', 'wp-seo-next'),
            '%pods:pod.field%' => __('Pods field (explicit pod) (if Custom Fields module enabled)', 'wp-seo-next'),
            '%woo:price%' => __('WooCommerce product data (e.g. price/sku/stock_status) (if Custom Fields module enabled)', 'wp-seo-next'),
            '%woo:attr:pa_color%' => __('WooCommerce product attribute (e.g. pa_color) (if Custom Fields module enabled)', 'wp-seo-next'),
            '%cf:key%' => __('Auto: tries ACF → Meta Box → JetEngine → Pods → native meta (if Custom Fields module enabled)', 'wp-seo-next'),
        ];
        $syntax = [];
        $syntax[] = __('Advanced syntax:', 'wp-seo-next');
        $syntax[] = '';
        $syntax[] = __('Fallback operator (first non-empty wins):', 'wp-seo-next');
        $syntax[] = '%meta:custom_title??title??sitename%';
        $syntax[] = '';
        $syntax[] = __('Transform pipeline:', 'wp-seo-next');
        $syntax[] = '%excerpt|strip|truncate(160)%';
        $syntax[] = '%acf:brand|lower??sitename%';
        $syntax[] = '';
        $syntax[] = __('Built-in transforms:', 'wp-seo-next');
        $syntax[] = '- lower, upper';
        $syntax[] = '- trim';
        $syntax[] = '- strip (strip tags)';
        $syntax[] = '- truncate(n, "…")';
        $syntax[] = '- prefix("text"), suffix("text")';
        $syntax[] = '';
        $syntaxText = implode("\n", $syntax);

        ?>
        <div class="wrap">
            <h1><?php echo esc_html__('Tokens & Variables', 'wp-seo-next'); ?></h1>
            <p class="description">
                <?php echo esc_html__('Use these tokens in Title/Description templates (Settings → Templates).', 'wp-seo-next'); ?>
            </p>

            <h2><?php echo esc_html__('Core tokens', 'wp-seo-next'); ?></h2>
            <table class="widefat striped" style="max-width: 980px;">
                <thead>
                    <tr>
                        <th style="width: 260px;"><?php echo esc_html__('Token', 'wp-seo-next'); ?></th>
                        <th><?php echo esc_html__('Meaning', 'wp-seo-next'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($baseTokens as $t => $d) : ?>
                        <tr>
                            <td><code><?php echo esc_html($t); ?></code></td>
                            <td><?php echo esc_html($d); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <h2 style="margin-top: 20px;"><?php echo esc_html__('Singular-only tokens', 'wp-seo-next'); ?></h2>
            <table class="widefat striped" style="max-width: 980px;">
                <thead>
                    <tr>
                        <th style="width: 260px;"><?php echo esc_html__('Token', 'wp-seo-next'); ?></th>
                        <th><?php echo esc_html__('Meaning', 'wp-seo-next'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($singularTokens as $t => $d) : ?>
                        <tr>
                            <td><code><?php echo esc_html($t); ?></code></td>
                            <td><?php echo esc_html($d); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <h2 style="margin-top: 20px;"><?php echo esc_html__('Term-only tokens', 'wp-seo-next'); ?></h2>
            <table class="widefat striped" style="max-width: 980px;">
                <thead>
                    <tr>
                        <th style="width: 260px;"><?php echo esc_html__('Token', 'wp-seo-next'); ?></th>
                        <th><?php echo esc_html__('Meaning', 'wp-seo-next'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($termTokens as $t => $d) : ?>
                        <tr>
                            <td><code><?php echo esc_html($t); ?></code></td>
                            <td><?php echo esc_html($d); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <h2 style="margin-top: 20px;"><?php echo esc_html__('Dynamic tokens (variable providers)', 'wp-seo-next'); ?></h2>
            <table class="widefat striped" style="max-width: 980px;">
                <thead>
                    <tr>
                        <th style="width: 260px;"><?php echo esc_html__('Token', 'wp-seo-next'); ?></th>
                        <th><?php echo esc_html__('Meaning', 'wp-seo-next'); ?></th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($dynamicTokens as $t => $d) : ?>
                        <tr>
                            <td><code><?php echo esc_html($t); ?></code></td>
                            <td><?php echo esc_html($d); ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>

            <h2 style="margin-top: 20px;"><?php echo esc_html__('Developer API (custom providers)', 'wp-seo-next'); ?></h2>
            <p class="description" style="max-width:980px;">
                <?php echo esc_html__('Developers can add new variable providers by hooking into wpsn/variables/resolve. Return an array with handled=true to mark it handled even if the value is empty.', 'wp-seo-next'); ?>
            </p>
            <pre style="max-width: 980px; background:#0b1220; color:#e9eef7; padding:12px 14px; border-radius:10px; border:1px solid rgba(0,0,0,.15); overflow:auto; white-space:pre;"><?php echo esc_html(
"add_filter('wpsn/variables/resolve', function(\$res, \$prefix, \$key, \$ctx) {\n" .
"    if (\$prefix !== 'pods') {\n" .
"        return \$res;\n" .
"    }\n" .
"    // Resolve your value here…\n" .
"    \$value = '';\n" .
"    return ['handled' => true, 'value' => \$value];\n" .
"}, 10, 4);" ); ?></pre>

            <h2 style="margin-top: 20px;"><?php echo esc_html__('Advanced syntax', 'wp-seo-next'); ?></h2>
            <pre style="max-width: 980px; background:#0b1220; color:#e9eef7; padding:12px 14px; border-radius:10px; border:1px solid rgba(0,0,0,.15); overflow:auto; white-space:pre;"><?php echo esc_html($syntaxText); ?></pre>

            <h2 style="margin-top: 20px;"><?php echo esc_html__('Template tester', 'wp-seo-next'); ?></h2>
            <p class="description">
                <?php echo esc_html__('Pick a context object (post/term/author/home) and render a template. This helps debug fallbacks and custom field tokens.', 'wp-seo-next'); ?>
            </p>

            <?php if ($submitted && $error !== '') : ?>
                <div class="notice notice-error is-dismissible"><p><?php echo esc_html($error); ?></p></div>
            <?php endif; ?>

            <form id="wpsn-tt-form" method="post" action="" style="max-width: 980px; background:#fff; border:1px solid rgba(0,0,0,.08); border-radius:12px; padding:12px 14px;">
                <?php wp_nonce_field(self::POST_NONCE_ACTION); ?>
                <input type="hidden" name="wpsn_tt_submit" value="1" />
                <div style="display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end;">
                    <div>
                        <label for="wpsn-tt-type"><strong><?php echo esc_html__('Context', 'wp-seo-next'); ?></strong></label><br/>
                        <select id="wpsn-tt-type" name="object_type">
                            <option value="post" <?php selected($type, 'post'); ?>><?php echo esc_html__('Post', 'wp-seo-next'); ?></option>
                            <option value="term" <?php selected($type, 'term'); ?>><?php echo esc_html__('Term', 'wp-seo-next'); ?></option>
                            <option value="author" <?php selected($type, 'author'); ?>><?php echo esc_html__('Author archive', 'wp-seo-next'); ?></option>
                            <option value="home" <?php selected($type, 'home'); ?>><?php echo esc_html__('Home', 'wp-seo-next'); ?></option>
                        </select>
                    </div>

                    <div id="wpsn-tt-post-wrap">
                        <label for="wpsn-tt-post-id"><strong><?php echo esc_html__('Post ID', 'wp-seo-next'); ?></strong></label><br/>
                        <input type="number" id="wpsn-tt-post-id" name="post_id" value="<?php echo esc_attr((string) max(0, $postIdValue)); ?>" min="1" placeholder="123" style="width:110px;" />
                    </div>

                    <div id="wpsn-tt-term-wrap" style="display:none;">
                        <label for="wpsn-tt-term-id"><strong><?php echo esc_html__('Term ID', 'wp-seo-next'); ?></strong></label><br/>
                        <input type="number" id="wpsn-tt-term-id" name="term_id" value="<?php echo esc_attr((string) max(0, $termIdValue)); ?>" min="1" placeholder="45" style="width:110px;" />
                    </div>

                    <div id="wpsn-tt-tax-wrap" style="display:none;">
                        <label for="wpsn-tt-tax"><strong><?php echo esc_html__('Taxonomy', 'wp-seo-next'); ?></strong></label><br/>
                        <input type="text" id="wpsn-tt-tax" name="taxonomy" value="<?php echo esc_attr($taxValue !== '' ? $taxValue : 'category'); ?>" placeholder="category" style="width:160px;" />
                    </div>

                    <div id="wpsn-tt-author-wrap" style="display:none;">
                        <label for="wpsn-tt-author-id"><strong><?php echo esc_html__('Author ID', 'wp-seo-next'); ?></strong></label><br/>
                        <input type="number" id="wpsn-tt-author-id" name="author_id" value="<?php echo esc_attr((string) max(0, $authorIdValue)); ?>" min="1" placeholder="7" style="width:110px;" />
                    </div>

                    <div style="flex:1; min-width: 320px;">
                        <label for="wpsn-tt-template"><strong><?php echo esc_html__('Template', 'wp-seo-next'); ?></strong></label><br/>
                        <input type="text" id="wpsn-tt-template" name="template" value="<?php echo esc_attr($templateValue !== '' ? $templateValue : '%title% %sep% %sitename%'); ?>" style="width:100%;" />
                    </div>

                    <div>
                        <button type="submit" class="button button-secondary" id="wpsn-tt-run"><?php echo esc_html__('Render', 'wp-seo-next'); ?></button>
                    </div>
                </div>

                <div class="wpsn-ajax-optional" style="margin-top:10px; display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
                    <label class="description" style="margin:0;">
                        <input type="checkbox" id="wpsn-tt-live" />
                        <?php echo esc_html__('Live mode (AJAX, optional)', 'wp-seo-next'); ?>
                    </label>
                    <span class="description" style="margin:0; opacity:.85;">
                        <?php echo esc_html__('If your host returns HTTP 503 for admin-ajax.php, keep this off (default).', 'wp-seo-next'); ?>
                    </span>
                </div>

                <p class="description" id="wpsn-tt-status" style="margin-top:10px; display:none;"></p>

                <div id="wpsn-tt-output-wrap" style="<?php echo ($submitted && $error === '') ? 'display:block' : 'display:none'; ?>; margin-top: 10px;">
                    <p style="margin:0 0 6px 0;"><strong><?php echo esc_html__('Output', 'wp-seo-next'); ?></strong></p>
                    <pre id="wpsn-tt-output" style="margin:0; background:#0b1220; color:#e9eef7; padding:12px 14px; border-radius:10px; border:1px solid rgba(0,0,0,.15); overflow:auto; white-space:pre-wrap;"><?php echo esc_html($submitted && $error === '' ? (($output !== '') ? $output : '(empty)') : ''); ?></pre>
                </div>

                <div id="wpsn-tt-debug-wrap" style="<?php echo ($submitted && $error === '') ? 'display:block' : 'display:none'; ?>; margin-top: 10px;">
                    <p style="margin:0 0 6px 0;"><strong><?php echo esc_html__('Token breakdown', 'wp-seo-next'); ?></strong></p>
                    <pre id="wpsn-tt-debug" style="margin:0; background:#0b1220; color:#e9eef7; padding:12px 14px; border-radius:10px; border:1px solid rgba(0,0,0,.15); overflow:auto; max-height: 320px; white-space:pre;"><?php echo esc_html($submitted && $error === '' ? $debugText : ''); ?></pre>
                </div>
            </form>
        </div>
        <?php
    }

    /**
     * @param array<int,array<string,mixed>> $tokens
     */
    private function formatTokenBreakdown(array $tokens): string
    {
        if (empty($tokens)) {
            return '(no tokens detected)';
        }

        $lines = [];
        foreach ($tokens as $t) {
            $token = isset($t['token']) ? (string) $t['token'] : '';
            $value = isset($t['value']) ? (string) $t['value'] : '';
            $selected = isset($t['selected_branch']) ? (string) $t['selected_branch'] : '';

            $lines[] = $token . ' => ' . ($value !== '' ? wp_json_encode($value, JSON_UNESCAPED_UNICODE) : '(empty)');

            $branches = isset($t['branches']) && is_array($t['branches']) ? $t['branches'] : [];
            if (!empty($branches)) {
                $lines[] = '  branches:';
                foreach ($branches as $b) {
                    $bx = isset($b['expr']) ? (string) $b['expr'] : '';
                    $bv = isset($b['value']) ? (string) $b['value'] : '';
                    $mark = ($selected !== '' && $bx === $selected) ? ' *' : '  ';
                    $lines[] = '   -' . $mark . ' ' . $bx . ' => ' . ($bv !== '' ? wp_json_encode($bv, JSON_UNESCAPED_UNICODE) : '(empty)');
                }
            }

            $lines[] = '';
        }

        return implode("\n", $lines);
    }

    public function ajaxTemplateTest(): void
    {
        if (!Capabilities::canManageTools()) {
            wp_send_json_error(['message' => __('You do not have permission to perform this action.', 'wp-seo-next')], 403);
        }

        check_ajax_referer(self::AJAX_NONCE_ACTION, 'nonce');

        $type = isset($_POST['object_type']) ? sanitize_key((string) wp_unslash($_POST['object_type'])) : 'post';
        $template = isset($_POST['template']) ? (string) wp_unslash($_POST['template']) : '';
        $template = trim($template);

        if ($template === '') {
            wp_send_json_error(['message' => __('Template is empty.', 'wp-seo-next')], 400);
        }

        // Safety: avoid extremely large payloads.
        if (function_exists('mb_strlen') && mb_strlen($template) > 4000) {
            $template = (string) mb_substr($template, 0, 4000);
        } elseif (strlen($template) > 4000) {
            $template = substr($template, 0, 4000);
        }

        $ctx = null;

        if ($type === 'home') {
            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_HOME, null, null, home_url('/'), 1);
        } elseif ($type === 'post') {
            $postId = isset($_POST['object_id']) ? (int) $_POST['object_id'] : 0;
            $postId = $postId > 0 ? $postId : (isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0);
            if ($postId <= 0) {
                wp_send_json_error(['message' => __('Invalid Post ID.', 'wp-seo-next')], 400);
            }
            $post = get_post($postId);
            if (!$post instanceof \WP_Post) {
                wp_send_json_error(['message' => __('Post not found.', 'wp-seo-next')], 404);
            }
            $url = get_permalink($post);
            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_SINGULAR, (int) $post->ID, (string) $post->post_type, is_string($url) ? $url : '', 1);
        } elseif ($type === 'term') {
            $termId = isset($_POST['object_id']) ? (int) $_POST['object_id'] : 0;
            $termId = $termId > 0 ? $termId : (isset($_POST['term_id']) ? (int) $_POST['term_id'] : 0);
            $tax = isset($_POST['taxonomy']) ? sanitize_key((string) wp_unslash($_POST['taxonomy'])) : '';
            if ($termId <= 0) {
                wp_send_json_error(['message' => __('Invalid Term ID.', 'wp-seo-next')], 400);
            }
            if ($tax === '') {
                wp_send_json_error(['message' => __('Taxonomy is required.', 'wp-seo-next')], 400);
            }
            $term = get_term($termId, $tax);
            if (!$term || is_wp_error($term)) {
                wp_send_json_error(['message' => __('Term not found.', 'wp-seo-next')], 404);
            }
            $link = get_term_link($term);
            $url = (!is_wp_error($link) && is_string($link)) ? $link : '';
            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_TERM, (int) $termId, (string) $tax, $url, 1);
        } elseif ($type === 'author') {
            $authorId = isset($_POST['author_id']) ? (int) $_POST['author_id'] : 0;
            if ($authorId <= 0) {
                wp_send_json_error(['message' => __('Invalid Author ID.', 'wp-seo-next')], 400);
            }
            $u = get_userdata($authorId);
            if (!$u) {
                wp_send_json_error(['message' => __('Author not found.', 'wp-seo-next')], 404);
            }
            $url = get_author_posts_url($authorId);
            $ctx = new SeoDocumentContext(SeoDocumentContext::TYPE_ARCHIVE, (int) $authorId, 'author', is_string($url) ? $url : '', 1);
        } else {
            wp_send_json_error(['message' => __('Unsupported context type.', 'wp-seo-next')], 400);
        }

        /** @var TemplateEngine $engine */
        $engine = $this->container->get('template_engine');

        $debug = $engine->renderWithDebug($template, $ctx);

        wp_send_json_success([
            'output' => (string) ($debug['output'] ?? ''),
            'tokens' => isset($debug['tokens']) ? $debug['tokens'] : [],
        ]);
    }
}
