<?php

namespace WPSN\SEO;

defined('ABSPATH') || exit;

/**
 * ContentExtractor
 *
 * A small, integration-friendly helper to extract meaningful page text for:
 * - local suggestions (rule-based)
 * - external AI prompts
 * - meta templates (e.g. %excerpt%)
 * - future readability checks
 *
 * It is intentionally dependency-free and uses WordPress filters so
 * integrations (Elementor/ACF/JetEngine/etc.) can plug in without coupling.
 */
final class ContentExtractor
{
    /**
     * Get best-effort HTML for a post.
     *
     * @param array{context?:string, apply_shortcodes?:bool} $args
     */
    public function getPostHtml(int $postId, array $args = []): string
    {
        $post = get_post($postId);
        if (!$post) {
            return '';
        }

        $context = isset($args['context']) ? (string) $args['context'] : 'analysis';

        // 1) Let builders provide rendered HTML.
        $html = (string) apply_filters('wpsn/content/builder_html', '', $postId, $context);
        if (is_string($html) && trim($html) !== '') {
            return (string) apply_filters('wpsn/content/html', $html, $postId, $context);
        }

        // 2) Fallback to post_content.
        $content = (string) $post->post_content;

        // Render blocks (Gutenberg).
        if (function_exists('do_blocks')) {
            $content = (string) do_blocks($content);
        }

        $applyShortcodes = array_key_exists('apply_shortcodes', $args) ? (bool) $args['apply_shortcodes'] : true;
        if ($applyShortcodes) {
            $content = (string) do_shortcode($content);
        }

        // Normalize paragraphs (helps some extractors).
        $content = (string) wpautop($content);

        return (string) apply_filters('wpsn/content/html', $content, $postId, $context);
    }

    /**
     * Get best-effort plain text for a post.
     *
     * @param array{context?:string, max_chars?:int, apply_shortcodes?:bool} $args
     */
    public function getPostText(int $postId, array $args = []): string
    {
        $context = isset($args['context']) ? (string) $args['context'] : 'analysis';
        $maxChars = isset($args['max_chars']) ? (int) $args['max_chars'] : 0;

        $html = $this->getPostHtml($postId, $args);
        $text = $this->extractPlainText($html);

        // Optional: allow integrations to contribute extra content text (e.g. custom fields).
        $extra = apply_filters('wpsn/content/extra_text', '', $postId, $context);
        if (is_array($extra)) {
            $extra = implode("\n", array_map('strval', $extra));
        }
        if (is_string($extra) && trim($extra) !== '') {
            $text = trim($text . "\n" . $this->extractPlainText($extra));
        }

        if ($maxChars > 0) {
            $text = $this->truncateChars($text, $maxChars);
        }

        return $text;
    }

    /**
     * Best-effort excerpt text:
     * - use explicit excerpt if present
     * - else derive from extracted content
     */
    public function getPostExcerptText(int $postId, int $maxWords = 28): string
    {
        $excerpt = (string) get_post_field('post_excerpt', $postId);
        $excerpt = trim((string) wp_strip_all_tags($excerpt));
        if ($excerpt !== '') {
            return $excerpt;
        }

        $text = $this->getPostText($postId, [
            'context' => 'excerpt',
        ]);
        $text = trim($text);
        if ($text === '') {
            return '';
        }

        return trim((string) wp_trim_words($text, max(5, $maxWords), '…'));
    }

    public function extractPlainText(string $html): string
    {
        $html = (string) $html;

        // Strip Gutenberg comments.
        $html = preg_replace('/<!--\s*\/?.*?\s*-->/s', ' ', $html) ?? $html;

        // Strip shortcodes.
        $html = strip_shortcodes($html);

        // Strip tags and decode entities.
        $text = wp_strip_all_tags($html, true);
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, get_bloginfo('charset'));

        // Normalize whitespace.
        $text = preg_replace('/\s+/u', ' ', $text) ?? $text;
        return trim($text);
    }

    private function truncateChars(string $text, int $maxChars): string
    {
        if ($maxChars <= 0) {
            return $text;
        }

        if (function_exists('mb_strlen') && function_exists('mb_substr')) {
            if (mb_strlen($text) > $maxChars) {
                return (string) mb_substr($text, 0, $maxChars);
            }
            return $text;
        }

        if (strlen($text) > $maxChars) {
            return substr($text, 0, $maxChars);
        }

        return $text;
    }
}
