<?php

namespace WPSN\Modules\IndexationRisk;

use WPSN\Core\Settings;
use WPSN\SEO\ContentExtractor;
use WPSN\SEO\InternalLinkEstimator;
use WPSN\SEO\MetaKeys;

defined('ABSPATH') || exit;

final class IndexationRiskCalculator
{
    private Settings $settings;
    private ContentExtractor $extractor;
    private InternalLinkEstimator $estimator;

    public function __construct(Settings $settings)
    {
        $this->settings = $settings;
        $this->extractor = new ContentExtractor();
        $this->estimator = new InternalLinkEstimator();
    }

    /**
     * @return array{post_id:int,post_type:string,url:string,title:string,score:int,inbound_links:int,word_count:int,in_sitemap:int,indexable:int,reasons:array<int,array<string,mixed>>}
     */
    public function calculateForPost(int $postId): array
    {
        $postId = (int) $postId;
        $post = $postId > 0 ? get_post($postId) : null;

        $postType = $post ? (string) $post->post_type : '';
        $url = $postId > 0 ? (string) get_permalink($postId) : '';
        $title = $postId > 0 ? (string) get_the_title($postId) : '';

        $indexable = $this->isIndexableSingular($postId);

        // Word count (best-effort).
        $text = '';
        if ($postId > 0) {
            try {
                $text = (string) $this->extractor->getPostText($postId, [
                    'context' => 'indexation_risk',
                    'max_chars' => 15000,
                    'apply_shortcodes' => false,
                ]);
            } catch (\Throwable $e) {
                $text = $post ? (string) $post->post_content : '';
            }
        }
        $wordCount = $this->wordCount($text);

        // Inbound internal links (best-effort estimate).
        $inbound = 0;
        if ($postId > 0 && $url !== '') {
            try {
                $inbound = (int) $this->estimator->estimateInbound($postId, $url);
            } catch (\Throwable $e) {
                $inbound = 0;
            }
        }

        // Sitemap presence (only if sitemap module is enabled).
        $sitemapEnabled = (bool) $this->settings->moduleEnabled('sitemap', true);
        $inSitemap = 0;
        if ($sitemapEnabled && $indexable && $postType !== '') {
            $enabledTypes = $this->enabledSitemapPostTypes();
            $inSitemap = in_array($postType, $enabledTypes, true) ? 1 : 0;
        }

        $reasons = [];

        // Score model: 100 = healthy, 0 = high risk / non-indexable.
        $score = 100;

        if (!$indexable) {
            $score = 0;
            $reasons[] = [
                'code' => 'noindex',
                'message' => __('Page is set to noindex.', 'wp-seo-next'),
            ];
        } else {
            if ($inbound <= 0) {
                $score -= 40;
                $reasons[] = [
                    'code' => 'orphan',
                    'message' => __('No inbound internal links detected (possible orphan).', 'wp-seo-next'),
                ];
            } elseif ($inbound === 1) {
                $score -= 20;
                $reasons[] = [
                    'code' => 'low_inbound',
                    'message' => __('Only 1 inbound internal link detected.', 'wp-seo-next'),
                ];
            }

            if ($wordCount < 150) {
                $score -= 25;
                $reasons[] = [
                    'code' => 'very_thin_content',
                    'message' => __('Very thin content (<150 words).', 'wp-seo-next'),
                ];
            } elseif ($wordCount < 300) {
                $score -= 15;
                $reasons[] = [
                    'code' => 'thin_content',
                    'message' => __('Thin content (<300 words).', 'wp-seo-next'),
                ];
            }

            if ($sitemapEnabled && !$inSitemap) {
                $score -= 20;
                $reasons[] = [
                    'code' => 'not_in_sitemap',
                    'message' => __('Not included in sitemap (post type disabled or excluded).', 'wp-seo-next'),
                ];
            }

            // Staleness (very soft signal).
            $ageDays = 0;
            if ($post && !empty($post->post_modified_gmt)) {
                $ts = strtotime((string) $post->post_modified_gmt);
                if ($ts) {
                    $ageDays = (int) floor((time() - $ts) / 86400);
                }
            }
            if ($ageDays > 365) {
                $score -= 10;
                $reasons[] = [
                    'code' => 'stale',
                    'message' => __('Content not updated in 12+ months.', 'wp-seo-next'),
                ];
            }

            $score = max(0, min(100, $score));
        }

        return [
            'post_id' => $postId,
            'post_type' => $postType,
            'url' => $url,
            'title' => $title,
            'score' => $score,
            'inbound_links' => $inbound,
            'word_count' => $wordCount,
            'in_sitemap' => $inSitemap,
            'indexable' => $indexable ? 1 : 0,
            'reasons' => $reasons,
        ];
    }

    private function wordCount(string $text): int
    {
        $t = trim(wp_strip_all_tags($text));
        if ($t === '') {
            return 0;
        }
        $parts = preg_split('/\s+/u', $t);
        if (!is_array($parts)) {
            return 0;
        }
        $parts = array_values(array_filter($parts, static function ($w) {
            return is_string($w) && trim($w) !== '';
        }));
        return count($parts);
    }

    /**
     * Determine indexability for singular context.
     */
    private function isIndexableSingular(int $postId): bool
    {
        $globalIndex = (bool) $this->settings->get('robots.contexts.singular.index', true);
        $meta = get_post_meta($postId, MetaKeys::ROBOTS_INDEX, true);
        $meta = is_string($meta) ? trim($meta) : '';

        // If global index = true, noindex meta disables.
        if ($globalIndex) {
            return $meta !== 'noindex';
        }

        // If global index = false, only explicit index meta enables.
        return $meta === 'index';
    }

    /**
     * @return string[]
     */
    private function enabledSitemapPostTypes(): array
    {
        $types = $this->settings->get('sitemap.post_types', []);
        if (!is_array($types) || empty($types)) {
            // Default: all public types except attachment.
            $pts = get_post_types(['public' => true], 'names');
            $out = [];
            foreach ($pts as $pt) {
                if ($pt === 'attachment') {
                    continue;
                }
                $out[] = (string) $pt;
            }
            return $out;
        }

        $out = [];
        foreach ($types as $pt => $enabled) {
            if (!is_string($pt)) {
                continue;
            }
            if (!empty($enabled)) {
                $out[] = sanitize_key($pt);
            }
        }

        // Fallback to default if settings were empty.
        if (empty($out)) {
            $pts = get_post_types(['public' => true], 'names');
            foreach ($pts as $pt) {
                if ($pt === 'attachment') {
                    continue;
                }
                $out[] = (string) $pt;
            }
        }

        return array_values(array_unique($out));
    }
}
