<?php

if (!defined('ABSPATH')) {
    exit;
}

class SEO_Fury_Post_Meta_Internal_Links {

    public function __construct() {
        add_action('wp_ajax_seo_fury_scan_internal_links', array($this, 'ajax_scan'));
        add_action('save_post', array($this, 'clear_link_cache'));
    }

    public function clear_link_cache($post_id) {
        delete_transient('sf_links_scan_' . $post_id);
    }

    public function render_tab($post) {
        $core = SEO_Fury_Core::get_instance();
        $pro = $core->get_pro_features();
        $is_pro = $pro && $pro->is_pro();
        $hide_branding = function_exists('seo_fury_hide_branding') ? seo_fury_hide_branding() : false;

        if (!$is_pro) {
            ?>
            <div style="padding: 20px; text-align: center;">
                <div style="background: var(--sf-primary-light, #EEF2FF); border-radius: 12px; padding: 30px 20px;">
                    <div style="font-size: 36px; margin-bottom: 10px;">🔗</div>
                    <h3 style="margin: 0 0 8px; color: var(--sf-gray-900, #1E293B);"><?php esc_html_e('Internal Linking Suggestions', 'seo-fury'); ?></h3>
                    <p style="color: var(--sf-gray-500, #64748B); font-size: 13px; margin: 0 0 15px;">
                        <?php esc_html_e('Scan your content to find internal linking opportunities. Get smart suggestions based on your existing pages, posts, and products.', 'seo-fury'); ?>
                    </p>
                    <span style="background: linear-gradient(135deg, #4F46E5, #7C3AED); color: #fff; font-size: 11px; padding: 4px 12px; border-radius: 12px; font-weight: 600;"><?php esc_html_e('PRO Feature', 'seo-fury'); ?></span>
                    <?php if (!$hide_branding): ?>
                        <br><br>
                        <a href="https://seo-fury.com/#pricing" target="_blank" style="color: var(--sf-primary, #4F46E5); font-weight: 600; text-decoration: none;"><?php esc_html_e('Upgrade to PRO', 'seo-fury'); ?> &rarr;</a>
                    <?php endif; ?>
                </div>
            </div>
            <?php
            return;
        }
        ?>
        <div class="seo-fury-internal-links" style="padding: 12px 0;">
            <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;">
                <h4 style="margin: 0; font-size: 13px; font-weight: 600; color: var(--sf-gray-900, #1E293B);">Internal Linking</h4>
                <button type="button" id="seo-fury-scan-links" class="button button-small" style="font-size: 12px;">Scan Content</button>
            </div>

            <div id="seo-fury-link-stats" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 15px;">
                <div style="background: var(--sf-primary-light, #EEF2FF); border-radius: 8px; padding: 10px; text-align: center;">
                    <div id="sf-stat-internal" style="font-size: 20px; font-weight: 700; color: var(--sf-primary, #4F46E5);">—</div>
                    <div style="font-size: 10px; color: var(--sf-gray-500, #64748B);">Internal</div>
                </div>
                <div style="background: var(--sf-success-light, #ECFDF5); border-radius: 8px; padding: 10px; text-align: center;">
                    <div id="sf-stat-external" style="font-size: 20px; font-weight: 700; color: var(--sf-success, #10B981);">—</div>
                    <div style="font-size: 10px; color: var(--sf-gray-500, #64748B);">External</div>
                </div>
                <div style="background: var(--sf-accent-light, #FEF3C7); border-radius: 8px; padding: 10px; text-align: center;">
                    <div id="sf-stat-suggestions" style="font-size: 20px; font-weight: 700; color: var(--sf-accent, #F59E0B);">—</div>
                    <div style="font-size: 10px; color: var(--sf-gray-500, #64748B);">Suggestions</div>
                </div>
            </div>

            <div id="seo-fury-link-suggestions" style="display: none;">
                <h5 style="margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--sf-gray-700, #334155);">Suggested Links</h5>
                <div id="seo-fury-suggestions-list" style="max-height: 300px; overflow-y: auto;"></div>
            </div>

            <div id="seo-fury-link-scan-empty" style="display: none; text-align: center; padding: 20px; color: var(--sf-gray-500, #64748B);">
                <div style="font-size: 24px; margin-bottom: 8px;">✅</div>
                <p style="margin: 0; font-size: 13px;">No linking opportunities found. Your content looks well-linked!</p>
            </div>

            <div id="seo-fury-link-loading" style="display: none; text-align: center; padding: 20px;">
                <div style="font-size: 24px; margin-bottom: 8px;">⏳</div>
                <p style="margin: 0; font-size: 13px; color: var(--sf-gray-500, #64748B);">Scanning content...</p>
            </div>

            <div id="seo-fury-orphan-pages" style="display: none; margin-top: 15px; border-top: 1px solid var(--sf-gray-200, #E2E8F0); padding-top: 12px;">
                <h5 style="margin: 0 0 8px; font-size: 12px; font-weight: 600; color: var(--sf-gray-700, #334155);">Orphan Pages <span style="font-weight: 400; color: var(--sf-gray-400);">(no inbound links)</span></h5>
                <div id="seo-fury-orphan-list"></div>
            </div>
        </div>
        <?php
    }

    public function ajax_scan() {
        check_ajax_referer('seo_fury_internal_links_nonce', 'nonce');
        if (!current_user_can('edit_posts')) {
            wp_send_json_error(__('Insufficient permissions', 'seo-fury'));
        }

        $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
        if (!$post_id) {
            wp_send_json_error('Invalid post ID');
        }

        $post = get_post($post_id);
        if (!$post) {
            wp_send_json_error('Post not found');
        }

        // Server-side caching (5 min, bypassed with force=1)
        $force = isset($_POST['force']) ? sanitize_text_field($_POST['force']) : '0';
        $cache_key = 'sf_links_scan_' . $post_id;
        if ($force !== '1') {
            $cached = get_transient($cache_key);
            if ($cached !== false) {
                wp_send_json_success($cached);
            }
        }

        // Get content — supports Elementor and other page builders.
        $content      = $post->post_content;
        $builder_html = $this->get_builder_html( $post_id );

        // For plain text matching, prefer builder-extracted text when available.
        // Strip headings (<h1>-<h6>) and <nav> — don't suggest links for heading or nav text.
        if ( $builder_html ) {
            $html_for_match = preg_replace( '/<h[1-6][^>]*>.*?<\/h[1-6]>/is', '', $builder_html );
            $html_for_match = preg_replace( '/<nav[^>]*>.*?<\/nav>/is', '', $html_for_match );
            $plain_content  = wp_strip_all_tags( $html_for_match );
        } else {
            $html_for_match = strip_shortcodes( $content );
            $html_for_match = preg_replace( '/<h[1-6][^>]*>.*?<\/h[1-6]>/is', '', $html_for_match );
            $html_for_match = preg_replace( '/<nav[^>]*>.*?<\/nav>/is', '', $html_for_match );
            $plain_content  = wp_strip_all_tags( $html_for_match );
        }

        // Also include excerpt and post title for broader matching.
        $extra_parts = array();
        if ( ! empty( $post->post_excerpt ) ) {
            $extra_parts[] = wp_strip_all_tags( $post->post_excerpt );
        }
        if ( ! empty( $extra_parts ) ) {
            $plain_content .= ' ' . implode( ' ', $extra_parts );
        }

        $plain_lower = mb_strtolower( $plain_content, 'UTF-8' );

        // For link stats and existing links, use BOTH rendered post_content
        // (which has real <a> tags from Elementor rendering) AND builder-extracted HTML
        // (which reconstructs widget-level links). This ensures we catch all existing links.
        $html_for_links = $content;
        if ( $builder_html ) {
            $html_for_links = $content . ' ' . $builder_html;
        }

        $link_stats = $this->get_link_stats( $html_for_links );

        $all_posts = get_posts(array(
            'post_type' => get_post_types(array('public' => true), 'names'),
            'post_status' => 'publish',
            'posts_per_page' => 500,
            'exclude' => array($post_id),
            'orderby' => 'title',
            'order' => 'ASC',
        ));

        $existing_links   = $this->get_existing_links( $html_for_links );
        $existing_anchors = $this->get_existing_anchors( $html_for_links );
        $suggestions      = array();

        foreach ($all_posts as $candidate) {
            $target_url = get_permalink($candidate->ID);

            $already_linked = false;
            foreach ($existing_links as $link) {
                if (rtrim($link, '/') === rtrim($target_url, '/')) {
                    $already_linked = true;
                    break;
                }
            }
            if ($already_linked) continue;

            $title = $candidate->post_title;
            $title_lower = mb_strtolower($title, 'UTF-8');
            $title_len = mb_strlen($title_lower, 'UTF-8');

            if ($title_len < 3) continue;

            // Skip if the title is already used as anchor text of an existing link.
            $anchor_overlap = false;
            foreach ( $existing_anchors as $anchor ) {
                // Exact match, or the title is contained within an existing anchor.
                if ( $anchor === $title_lower || mb_strpos( $anchor, $title_lower ) !== false ) {
                    $anchor_overlap = true;
                    break;
                }
            }
            if ( $anchor_overlap ) continue;

            // Whole-word match only (prevents "акции" matching inside "реакции").
            // NOTE: \b does NOT work for Cyrillic because \w is only [a-zA-Z0-9_].
            // Use Unicode letter boundaries: (?<!\p{L}) and (?!\p{L}).
            $escaped_title = preg_quote($title_lower, '/');
            if (preg_match('/(?<!\p{L})' . $escaped_title . '(?!\p{L})/u', $plain_lower)) {
                $suggestions[] = array(
                    'match_text' => $title,
                    'target_title' => $candidate->post_title,
                    'target_url' => $target_url,
                    'post_type' => $candidate->post_type,
                    'score' => 100,
                    'match_type' => 'exact',
                );
                continue;
            }

            $words = preg_split('/[\s\-_,.:;!?()]+/u', $title_lower);
            $words = array_filter($words, function($w) {
                $stop = array('the','a','an','and','or','but','in','on','at','to','for','of','is','it','by','as','с','и','в','на','по','для','из','от','до','не','что','как','это','все','его','она','они','мы','вы','об','о');
                return mb_strlen($w, 'UTF-8') > 2 && !in_array($w, $stop);
            });
            $words = array_values($words);

            if (count($words) === 1 && mb_strlen($words[0], 'UTF-8') >= 4) {
                if (preg_match('/(?<!\p{L})' . preg_quote($words[0], '/') . '(?!\p{L})/ui', $plain_lower)) {
                    $suggestions[] = array(
                        'match_text' => $title,
                        'target_title' => $candidate->post_title,
                        'target_url' => $target_url,
                        'post_type' => $candidate->post_type,
                        'score' => 80,
                        'match_type' => 'word',
                    );
                    continue;
                }
            }

            if (count($words) >= 2) {
                $matched = false;
                for ($i = 0; $i <= count($words) - 2; $i++) {
                    $phrase = $words[$i] . ' ' . $words[$i+1];
                    if (mb_strpos($plain_lower, $phrase) !== false) {
                        // Skip if this phrase is already inside an existing anchor.
                        if ( $this->text_in_anchors( $phrase, $existing_anchors ) ) {
                            continue;
                        }
                        $suggestions[] = array(
                            'match_text' => $phrase,
                            'target_title' => $candidate->post_title,
                            'target_url' => $target_url,
                            'post_type' => $candidate->post_type,
                            'score' => 50 + count($words) * 5,
                            'match_type' => 'partial',
                        );
                        $matched = true;
                        break;
                    }
                }

                if (!$matched) {
                    foreach ($words as $word) {
                        if (mb_strlen($word, 'UTF-8') >= 5 && preg_match('/(?<!\p{L})' . preg_quote($word, '/') . '(?!\p{L})/ui', $plain_lower)) {
                            if ( $this->text_in_anchors( $word, $existing_anchors ) ) {
                                continue;
                            }
                            $suggestions[] = array(
                                'match_text' => $word,
                                'target_title' => $candidate->post_title,
                                'target_url' => $target_url,
                                'post_type' => $candidate->post_type,
                                'score' => 40,
                                'match_type' => 'word',
                            );
                            break;
                        }
                    }
                }
            }
        }

        usort($suggestions, function($a, $b) {
            return $b['score'] - $a['score'];
        });
        $suggestions = array_slice($suggestions, 0, 20);

        $orphan_pages = $this->get_orphan_pages($post_id, $all_posts);

        // Check for broken internal links (HEAD request, max 5)
        $broken_links = array();
        $home = home_url();
        $internal_urls = array_filter($existing_links, function($url) use ($home) {
            return strpos($url, $home) === 0 || (strpos($url, '/') === 0 && strpos($url, '//') !== 0);
        });
        $check_urls = array_slice(array_values($internal_urls), 0, 5);
        foreach ($check_urls as $check_url) {
            $full_url = strpos($check_url, 'http') === 0 ? $check_url : $home . $check_url;
            $head = wp_remote_head($full_url, array('timeout' => 3, 'redirection' => 0, 'sslverify' => false));
            if (is_wp_error($head) || wp_remote_retrieve_response_code($head) >= 400) {
                $broken_links[] = $check_url;
            }
        }

        $result_data = array(
            'stats' => $link_stats,
            'suggestions' => $suggestions,
            'orphan_pages' => array_slice($orphan_pages, 0, 10),
            'broken_links' => $broken_links,
        );

        // Cache for 5 minutes
        set_transient($cache_key, $result_data, 300);

        wp_send_json_success($result_data);
    }

    private function get_link_stats($content) {
        $internal = 0;
        $external = 0;
        $home = home_url();

        preg_match_all('/<a\s[^>]*href=["\']([^"\']+)["\'][^>]*>/i', $content, $matches);
        if (!empty($matches[1])) {
            foreach ($matches[1] as $url) {
                if (strpos($url, '#') === 0 || strpos($url, 'mailto:') === 0 || strpos($url, 'tel:') === 0) continue;
                if (strpos($url, $home) === 0 || strpos($url, '/') === 0) {
                    $internal++;
                } else {
                    $external++;
                }
            }
        }

        return array(
            'internal' => $internal,
            'external' => $external,
            'total' => $internal + $external,
        );
    }

    private function get_existing_links($content) {
        $urls = array();
        preg_match_all('/<a\s[^>]*href=["\']([^"\']+)["\'][^>]*>/i', $content, $matches);
        if (!empty($matches[1])) {
            $urls = $matches[1];
        }
        return $urls;
    }

    /**
     * Extract anchor texts from all existing links in the content.
     *
     * @param  string $html
     * @return array  Lowercase anchor texts.
     */
    private function get_existing_anchors( $html ) {
        $anchors = array();
        if ( preg_match_all( '/<a\s[^>]*>(.*?)<\/a>/is', $html, $matches ) ) {
            foreach ( $matches[1] as $inner ) {
                $text = trim( wp_strip_all_tags( $inner ) );
                if ( $text !== '' ) {
                    $anchors[] = mb_strtolower( $text, 'UTF-8' );
                }
            }
        }
        return array_unique( $anchors );
    }

    private function get_orphan_pages($current_id, $all_posts) {
        global $wpdb;
        $home = home_url();
        $orphans = array();

        // Build URL map for all candidates
        $candidates = array();
        foreach ($all_posts as $candidate) {
            if ($candidate->ID == $current_id) continue;
            $url = get_permalink($candidate->ID);
            $relative = str_replace($home, '', $url);
            $candidates[] = array(
                'post' => $candidate,
                'url' => $url,
                'relative' => $relative,
            );
        }

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

        // Batch query: get all post_content from published posts (excluding current)
        // and check which candidate URLs appear in any content
        $batch_size = 50;
        $batches = array_chunk($candidates, $batch_size);

        foreach ($batches as $batch) {
            $like_conditions = array();
            $like_values = array();

            foreach ($batch as $c) {
                $like_conditions[] = '(post_content LIKE %s OR post_content LIKE %s)';
                $like_values[] = '%' . $wpdb->esc_like($c['url']) . '%';
                $like_values[] = '%' . $wpdb->esc_like($c['relative']) . '%';
            }

            // Single query to find which URLs exist in any published post
            $where_likes = implode(' OR ', $like_conditions);
            $query = $wpdb->prepare(
                "SELECT post_content FROM {$wpdb->posts} WHERE post_status = 'publish' AND ID != %d AND ({$where_likes})",
                array_merge(array($current_id), $like_values)
            );
            $contents = $wpdb->get_col($query); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above via $wpdb->prepare()
            $all_content = implode(' ', $contents);

            foreach ($batch as $c) {
                // Check if this URL appears in any of the found content
                if (stripos($all_content, $c['url']) === false && stripos($all_content, $c['relative']) === false) {
                    $orphans[] = array(
                        'title' => $c['post']->post_title,
                        'url' => $c['url'],
                        'post_type' => $c['post']->post_type,
                    );
                }

                if (count($orphans) >= 10) break 2;
            }
        }

        return $orphans;
    }

    /**
     * Check if a text fragment is already part of an existing anchor text.
     *
     * @param  string $text    Lowercase text to check.
     * @param  array  $anchors Array of lowercase existing anchor texts.
     * @return bool
     */
    private function text_in_anchors( $text, $anchors ) {
        foreach ( $anchors as $anchor ) {
            if ( $text === $anchor || mb_strpos( $anchor, $text ) !== false ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get HTML content from page builders (Elementor, Beaver Builder, Brizy, etc.).
     *
     * @param  int         $post_id
     * @return string|false HTML string or false if no builder detected.
     */
    private function get_builder_html( $post_id ) {
        // 1. Elementor.
        $el_data = get_post_meta( $post_id, '_elementor_data', true );
        if ( ! empty( $el_data ) ) {
            $html = $this->extract_elementor_html( $el_data );
            if ( $html ) {
                return $html;
            }
        }

        // 2. Beaver Builder.
        $bb_data = get_post_meta( $post_id, '_fl_builder_data', true );
        if ( ! empty( $bb_data ) && is_array( $bb_data ) ) {
            $texts = array();
            array_walk_recursive( $bb_data, function ( $value ) use ( &$texts ) {
                if ( is_string( $value ) && strlen( $value ) > 10 ) {
                    $texts[] = $value;
                }
            } );
            $result = implode( ' ', $texts );
            return $result ?: false;
        }

        // 3. Brizy.
        $brizy = get_post_meta( $post_id, '_brizy_post_compiled_html_body', true );
        if ( ! empty( $brizy ) ) {
            return $brizy;
        }

        return false;
    }

    /**
     * Extract HTML content (with links preserved) from Elementor JSON data.
     *
     * @param  string      $json_string
     * @return string|false
     */
    private function extract_elementor_html( $json_string ) {
        $data = is_string( $json_string ) ? json_decode( $json_string, true ) : $json_string;
        if ( ! is_array( $data ) ) {
            return false;
        }

        $texts = array();
        $this->walk_elementor_for_html( $data, $texts );

        $result = implode( ' ', $texts );
        $result = preg_replace( '/\s+/', ' ', trim( $result ) );
        return $result ?: false;
    }

    /**
     * Recursively walk Elementor elements and collect HTML content (preserving links).
     */
    /**
     * Check if an Elementor widget type is non-content (navigation, menu, etc.)
     * and should be excluded from internal linking scan.
     */
    private function is_non_content_widget( $widget_type ) {
        static $skip = array(
            'nav-menu', 'wp-menu', 'navigation', 'mega-menu',
            'breadcrumbs', 'site-logo', 'site-title', 'sitemap',
            'menu-anchor', 'sidebar', 'wp-widget-nav_menu',
            'global', 'template', 'theme-post-navigation',
            'post-navigation', 'author-box', 'post-comments',
            'search-form', 'login',
            'share-buttons', 'social-icons',
            // JetMenu / JetBlocks
            'jet-menu', 'jet-mega-menu', 'jet-hamburger-menu', 'jet-breadcrumbs',
            // Premium Addons
            'premium-nav-menu', 'premium-mega-menu',
            // Essential Addons
            'eael-simple-menu', 'eael-navigation-menu',
            // PowerPack / Ultimate Addons
            'pp-advanced-menu', 'uael-nav-menu',
        );
        return in_array( $widget_type, $skip, true );
    }

    private function walk_elementor_for_html( $elements, &$texts ) {
        foreach ( $elements as $el ) {
            if ( ! is_array( $el ) ) {
                continue;
            }

            // Skip non-content widgets (menus, navigation, breadcrumbs, etc.).
            if ( ! empty( $el['widgetType'] ) && $this->is_non_content_widget( $el['widgetType'] ) ) {
                continue;
            }

            if ( ! empty( $el['settings'] ) && is_array( $el['settings'] ) ) {
                $s = $el['settings'];
                // Detect widget-level link (heading with link, button, icon-box, etc.)
                $widget_link = ! empty( $s['link']['url'] ) ? $s['link']['url'] : '';
                // Is this a heading widget? Wrap title in <h> so we can strip it later
                // (links should not be suggested for text inside headings — bad SEO).
                $is_heading = ! empty( $el['widgetType'] ) && in_array( $el['widgetType'], array( 'heading', 'animated-headline' ), true );
                // Plain text fields that can have widget-level links.
                $plain_fields = array( 'title', 'description', 'text', 'title_text', 'button_text' );
                // Rich HTML fields that already contain their own <a> tags.
                $html_fields  = array( 'editor', 'content', 'html' );

                foreach ( $plain_fields as $field ) {
                    if ( ! empty( $s[ $field ] ) && is_string( $s[ $field ] ) ) {
                        $val = $s[ $field ];
                        // Heading widget title → wrap in <h> tag (stripped before matching).
                        if ( $is_heading && $field === 'title' ) {
                            $tag = ! empty( $s['header_size'] ) ? $s['header_size'] : 'h2';
                            $texts[] = '<' . $tag . '>' . $val . '</' . $tag . '>';
                        // Wrap in <a> if this widget has a link, so anchor detection works.
                        } elseif ( $widget_link && strpos( $val, '<a' ) === false ) {
                            $texts[] = '<a href="' . esc_url( $widget_link ) . '">' . $val . '</a>';
                        } else {
                            $texts[] = $val;
                        }
                    }
                }
                foreach ( $html_fields as $field ) {
                    if ( ! empty( $s[ $field ] ) && is_string( $s[ $field ] ) ) {
                        $texts[] = $s[ $field ];
                    }
                }

                // Tabs / Accordion items.
                if ( ! empty( $s['tabs'] ) && is_array( $s['tabs'] ) ) {
                    foreach ( $s['tabs'] as $tab ) {
                        if ( ! empty( $tab['tab_content'] ) && is_string( $tab['tab_content'] ) ) {
                            $texts[] = $tab['tab_content'];
                        }
                    }
                }

                // Icon list items.
                if ( ! empty( $s['icon_list'] ) && is_array( $s['icon_list'] ) ) {
                    foreach ( $s['icon_list'] as $item ) {
                        if ( ! empty( $item['text'] ) && is_string( $item['text'] ) ) {
                            $texts[] = $item['text'];
                        }
                    }
                }
            }

            if ( ! empty( $el['elements'] ) && is_array( $el['elements'] ) ) {
                $this->walk_elementor_for_html( $el['elements'], $texts );
            }
        }
    }
}
