<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ -- NUCLEAR+++
// File: /home/ploi/automarketer-ai.com/public/wp-content/plugins/automarketer-ai/includes/class-bg-optimizer.php
// Description: Background content optimizer -- runs SEO and readability fixes on newly created blog posts via cron - NUCLEAR+++
// Last Edited: 2026-05-13
// ==================================================

namespace AutoMarketerAI;

defined('ABSPATH') || exit;

class BgOptimizer
{
    public const CRON_HOOK = 'amai_bg_optimize_post';

    // Maximum fix passes per run -- prevents runaway API calls
    private const MAX_PASSES = 5;

    // Fields auto-applied server-side -- meta_title and meta_description
    // require human review and are intentionally excluded
    private const AUTO_APPLY_FIELDS = [
        'post_title',
        'post_content',
        'post_content_prepend',
        'post_tags',
        'meta_title',
        'meta_description',
        'post_slug',
        'internal_links',
    ];

    // --------------------------------------------------
    // Register hooks
    // --------------------------------------------------
    public static function register(): void
    {
        add_action(self::CRON_HOOK, [self::class, 'run']);
    }

    // --------------------------------------------------
    // Main entry point -- called by cron with post ID
    // --------------------------------------------------
    public static function run(int $post_id): void
    {
        if ($post_id <= 0) {
            return;
        }

        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            return;
        }

        // Only optimize posts -- never pages or custom post types
        if ($post->post_type !== 'post') {
            return;
        }

        // content-optimizer module must be active
        if (!in_array('content-optimizer', (array) get_option('amai_active_modules', []), true)) {
            return;
        }

        $succeeded = [];

        for ($pass = 0; $pass < self::MAX_PASSES; $pass++) {
            // Re-analyze on every pass to get fresh issues and populate transients
            $result = SeoAnalyzer::analyze($post_id);
            $issues = $result['issues'] ?? [];

            // Find first fixable issue not yet succeeded and in auto-apply list
            $issue = self::pickNextIssue($issues, $succeeded);
            if ($issue === null) {
                // No more fixable issues -- done
                break;
            }

            $issue_index = (int) ($issue['issue_index'] ?? -1);
            $field       = (string) ($issue['field']       ?? '');
            $fix_mode    = (string) ($issue['fix_mode']    ?? 'direct');

            if ($issue_index < 0 || $field === '') {
                break;
            }

            // Pull fix prompt from transient populated by SeoAnalyzer::analyze()
            $prompts         = get_transient('amai_seo_prompts_' . $post_id);
            $transient_entry = (is_array($prompts) && isset($prompts[$issue_index])) ? $prompts[$issue_index] : null;
            $fix_prompt      = is_array($transient_entry) ? (string) ($transient_entry['prompt'] ?? '') : '';
            $allowed_indexes = (is_array($transient_entry) && !empty($transient_entry['allowed_indexes']))
                ? array_map('intval', $transient_entry['allowed_indexes'])
                : [];

            if ($fix_prompt === '') {
                $succeeded[] = $issue_index;
                continue;
            }

            $result_text = SeoMetaBox::callCoreFixIt($fix_prompt);
            if ($result_text === null) {
                // Core call failed -- stop this run, cron will not retry automatically
                break;
            }

            // Apply the fix server-side
            $applied = self::applyFix($post_id, $field, $fix_mode, $result_text, $allowed_indexes);
            if ($applied) {
                $succeeded[] = $issue_index;
            } else {
                // Could not apply -- skip this issue and continue
                $succeeded[] = $issue_index;
            }
        }
    }

    // --------------------------------------------------
    // Pick the next fixable issue not already succeeded
    // Prioritises errors over warnings over suggestions
    // Only returns issues in the auto-apply field list
    // --------------------------------------------------
    private static function pickNextIssue(array $issues, array $succeeded): ?array
    {
        $severity_rank = ['error' => 0, 'warning' => 1, 'suggestion' => 2];

        $candidates = [];
        foreach ($issues as $issue) {
            $field       = (string) ($issue['field']       ?? '');
            $issue_index = (int)    ($issue['issue_index'] ?? -1);
            $fix_mode    = (string) ($issue['fix_mode']    ?? '');

            if ($issue_index < 0) {
                continue;
            }
            if (in_array($issue_index, $succeeded, true)) {
                continue;
            }
            if (!in_array($field, self::AUTO_APPLY_FIELDS, true)) {
                continue;
            }
            // Must have a fix mode set -- issues with empty fix_mode have no AI fix
            if ($fix_mode === '') {
                continue;
            }

            $candidates[] = $issue;
        }

        if (empty($candidates)) {
            return null;
        }

        usort($candidates, static function (array $a, array $b) use ($severity_rank): int {
            $ra = $severity_rank[$a['severity'] ?? ''] ?? 9;
            $rb = $severity_rank[$b['severity'] ?? ''] ?? 9;
            return $ra - $rb;
        });

        return $candidates[0];
    }

    // --------------------------------------------------
    // Apply a fix result to the post
    // Mirrors the server-side save logic in SeoMetaBox::handleFixIt()
    // --------------------------------------------------
    private static function applyFix(
        int $post_id,
        string $field,
        string $fix_mode,
        string $result,
        array $allowed_indexes
    ): bool {
        $post = get_post($post_id);
        if (!$post instanceof \WP_Post) {
            return false;
        }

        if ($field === 'post_title') {
            $updated = wp_update_post(['ID' => $post_id, 'post_title' => $result]);
            return !is_wp_error($updated) && $updated > 0;
        }

        if ($field === 'post_content') {
            if ($fix_mode === 'paragraphs') {
                $clean   = trim((string) preg_replace('/^```json|```$/m', '', $result));
                $patches = json_decode($clean, true);
                if (!is_array($patches)) {
                    return false;
                }
                if (!empty($allowed_indexes)) {
                    $patches = array_values(array_filter($patches, static function (array $p) use ($allowed_indexes): bool {
                        return isset($p['index']) && in_array((int) $p['index'], $allowed_indexes, true);
                    }));
                }
                if (empty($patches)) {
                    return false;
                }
                $patch_map = [];
                foreach ($patches as $p) {
                    if (isset($p['index']) && !empty($p['fixed'] ?? $p['html'] ?? '')) {
                        $patch_map[(int) $p['index']] = $p['fixed'] ?? $p['html'];
                    }
                }
                $raw_content = $post->post_content;
                $counter     = 0;
                $updated_content = preg_replace_callback(
                    '/<p(\s[^>]*)?>([\s\S]*?)<\/p>/i',
                    static function (array $m) use (&$counter, $patch_map): string {
                        $idx = $counter++;
                        return isset($patch_map[$idx]) ? $patch_map[$idx] : $m[0];
                    },
                    $raw_content
                );
                if ($updated_content === null || $updated_content === $raw_content) {
                    return false;
                }
                $updated_content = str_replace("\r", '', $updated_content);
                $updated_content = \AutoMarketerAI\LinkPreserver::preserve($post_id, $updated_content);
                $res = wp_update_post(['ID' => $post_id, 'post_content' => $updated_content]);
                return !is_wp_error($res) && $res > 0;
            }
            // Direct mode
            $result = str_replace("\r", '', $result);
            $result = \AutoMarketerAI\LinkPreserver::preserve($post_id, $result);
            $res    = wp_update_post(['ID' => $post_id, 'post_content' => $result]);
            return !is_wp_error($res) && $res > 0;
        }

        if ($field === 'post_content_prepend') {
            $h2_clean = trim((string) preg_replace('/^```html?|```$/m', '', $result));
            $h2_clean = strip_tags($h2_clean, '<h2>');
            if (preg_match('/<h2[^>]*>.*?<\/h2>/is', $h2_clean, $m)) {
                $new_content = $m[0] . "\n" . $post->post_content;
                $new_content = \AutoMarketerAI\LinkPreserver::preserve($post_id, $new_content);
                $res = wp_update_post(['ID' => $post_id, 'post_content' => $new_content]);
                return !is_wp_error($res) && $res > 0;
            }
            return false;
        }

        if ($field === 'post_tags') {
            $raw_tags   = array_map('trim', explode(',', $result));
            $clean_tags = array_values(array_filter($raw_tags, static function (string $t): bool {
                return $t !== '';
            }));
            if (empty($clean_tags)) {
                return false;
            }
            wp_set_post_tags($post_id, $clean_tags, false);
            return true;
        }

        if ($field === 'meta_title') {
            if (mb_strlen($result) > 100) {
                return false;
            }
            SeoData::set($post_id, 'meta_title', $result);
            return true;
        }

        if ($field === 'meta_description') {
            if (mb_strlen($result) > 200) {
                return false;
            }
            SeoData::set($post_id, 'meta_description', $result);
            return true;
        }

        if ($field === 'post_slug') {
            $slug = sanitize_title($result);
            if ($slug === '') {
                return false;
            }
            $res = wp_update_post(['ID' => $post_id, 'post_name' => $slug]);
            return !is_wp_error($res) && $res > 0;
        }
        if ($field === 'internal_links') {
            $updated = SeoMetaBox::buildInternalLinksContent($post);
            if ($updated === null || $updated === $post->post_content) {
                return false;
            }
            $updated = \AutoMarketerAI\LinkPreserver::preserve($post_id, $updated);
            $res = wp_update_post(['ID' => $post_id, 'post_content' => $updated]);
            return !is_wp_error($res) && $res > 0;
        }
        return false;
    }
}