<?php

/**
 * Work with terms
 */
class Wpil_Term
{
    /**
     * Register services
     */
    public function register()
    {
        foreach (Wpil_Settings::getTermTypes() as $term) {
            add_action($term . '_add_form_fields', [$this, 'showTermSuggestions']);
            add_action($term . '_edit_form', [$this, 'showTermSuggestions']);
            add_action('edited_' . $term, [$this, 'addLinksToTerm']);
            add_action('edited_' . $term, ['Wpil_TargetKeyword', 'update_keywords_on_term_save']);
            add_action($term . '_add_form_fields', [$this, 'showTargetKeywords']);
            add_action($term . '_edit_form', [$this, 'showTargetKeywords']);
            // check the term link counts once were sure there's no more link processing to do
            add_action('saved_' . $term, [$this, 'updateTermStats'], 10, 3);
        }
    }

    /**
     * Show suggestions on term page
     */
    public static function showTermSuggestions()
    {
        if(empty($_GET['tag_ID']) ||empty($_GET['taxonomy'] || !in_array($_GET['taxonomy'], Wpil_Settings::getTermTypes()))){
            return;
        }

        $term_id = (int)$_GET['tag_ID'];
        $post_id = 0;
        $user = wp_get_current_user();
        $manually_trigger_suggestions = !empty(get_option('wpil_manually_trigger_suggestions', false));

        // exit if the term has been ignored
        $completely_ignored = Wpil_Settings::get_completely_ignored_pages();
        if(!empty($completely_ignored) && in_array('term_' . $term_id, $completely_ignored, true)){
            return;
        }

        ?>
        <div id="wpil_link-articles" class="postbox">
            <h2 class="hndle no-drag"><span><?php esc_html_e('Link Whisper Suggested Links', 'wpil'); ?></span></h2>
            <div class="inside">
                <?php include WP_INTERNAL_LINKING_PLUGIN_DIR . '/templates/link_list_v2.php';?>
            </div>
        </div>
        <?php
    }

    /**
     * Show target keywords on term page
     */
    public static function showTargetKeywords()
    {
        if(empty($_GET['tag_ID']) ||empty($_GET['taxonomy'] || !in_array($_GET['taxonomy'], Wpil_Settings::getTermTypes()))){
            return;
        }

        $term_id = (int)$_GET['tag_ID'];
        $post_id = 0;
        $user = wp_get_current_user();
        $post = new Wpil_Model_Post($term_id, 'term');

        // exit if the term has been ignored
        $completely_ignored = Wpil_Settings::get_completely_ignored_pages();
        if(!empty($completely_ignored) && in_array($post->type . '_' . $post->id, $completely_ignored, true)){
            return;
        }

        $keywords = Wpil_TargetKeyword::get_keywords_by_post_ids($term_id, 'term');
        $keyword_sources = Wpil_TargetKeyword::get_active_keyword_sources();
        $is_metabox = true;
        ?>
        <div id="wpil_target-keywords" class="postbox ">
            <h2 class="hndle no-drag"><span><?php esc_html_e('Link Whisper Target Keywords', 'wpil'); ?></span></h2>
            <div class="inside"><?php
                include WP_INTERNAL_LINKING_PLUGIN_DIR . '/templates/target_keyword_list.php';
            ?>
            </div>
        </div>
        <?php
    }
    /**
     * Add links to term description on term update
     *
     * @param $term_id
     */
    public static function addLinksToTerm($term_id)
    {
        global $wpdb;

        //get links
        $meta = Wpil_Toolbox::get_encoded_term_meta($term_id,'wpil_links', true);

        if (!empty($meta)) {
            $taxonomies = Wpil_Query::taxonomyTypes();
            $description = $wpdb->get_col($wpdb->prepare("SELECT `description` FROM {$wpdb->term_taxonomy} WHERE term_id = %d {$taxonomies} LIMIT 1", $term_id));

            if(!empty($description)){
                if(is_array($description)){
                    $description = $description[0];
                }

                //add links to the term description
                foreach ($meta as $link) {
                    $force_insert = (isset($link['keyword_data']) && !empty($link['keyword_data']->force_insert)) ? true: false;
                    $changed_sentence = Wpil_Post::getSentenceWithAnchor($link);
                    $inserted = Wpil_Post::insertLink($description, $link['sentence'], $changed_sentence, $force_insert);
                    if($inserted){
                        Wpil_Keyword::update_auto_imported_link_count(1); // the function checcks to make sure that we're only counting during the link importing
                    }
                }
            }

            //update term description
            $wpdb->query($wpdb->prepare("UPDATE {$wpdb->prefix}term_taxonomy SET description = %s WHERE term_id = {$term_id} AND description != ''", $description));

            // add links to meta field content areas
            $description .= self::addLinkToMetaContent($term_id);
            // add links to the ACF fields
            $description .= self::addLinkToAdvancedCustomFields($term_id);

            // say that the link has been inserted
            Wpil_Base::track_action('link_inserted', true);

            //delete links from DB
            delete_term_meta($term_id, 'wpil_links'); // we can delete the meta at this pointe because there's no pagebuilders that support terms (that we support anywhay)
        }

        if (get_option('wpil_post_procession', 0) < (time() - 300)) { // checking if links are being inserted by the autolinker so we don't wind up in a loop.
            $term = new Wpil_Model_Post($term_id, 'term');
            if(!Wpil_Settings::disable_autolink_on_post_save()){
                Wpil_Keyword::addKeywordsToPost($term);
            }
            Wpil_URLChanger::replacePostURLs($term);
        }

        if(Wpil_Base::action_happened('link_inserted') || Wpil_Base::action_happened('link_url_updated')){
            self::updateTermStats($term_id, false, false);
        }
    }

    /**
     * Updates the term's linking stats after the link adding is completed elsewhere
     **/
    public static function updateTermStats($term_id, $tt_id, $updated){
        $term = new Wpil_Model_Post($term_id, 'term');
        if(WPIL_STATUS_LINK_TABLE_EXISTS && Wpil_Report::stored_link_content_changed($term)){
            // get the fresh term content for the benefit of the descendent methods
            $term->getFreshContent();
            // find any inbound internal link references that are no longer valid
            $removed_links = Wpil_Report::find_removed_report_inbound_links($term);
            // update the links stored in the link table
            Wpil_Report::update_post_in_link_table($term);
            // update the meta data for the term
            Wpil_Report::statUpdate($term, true);
            // and update the link counts for the posts that this one links to
            Wpil_Report::updateReportInternallyLinkedPosts($term, $removed_links);
        }
    }

    /**
     * Adds links to the term's meta fields
     **/
    public static function addLinkToMetaContent($term_id){
        $meta = Wpil_Toolbox::get_encoded_term_meta($term_id, 'wpil_links', true);

        if (!empty($meta)) {
            $fields = Wpil_Post::getMetaContentFieldList('term');
            if(!empty($fields)){
                foreach($fields as $field){
                    if($content = get_term_meta($term_id, $field, true)){
                        foreach($meta as $link){
                            if(strpos($content, $link['sentence']) !== false){
                                $force_insert = (isset($link['keyword_data']) && !empty($link['keyword_data']->force_insert)) ? true: false;
                                $changed_sentence = Wpil_Post::getSentenceWithAnchor($link);
                                Wpil_Post::insertLink($content, $link['sentence'], $changed_sentence, $force_insert);
                            }
                        }
                        update_term_meta($term_id, $field, $content);
                    }
                }
            }

            /**
             * Add the links to any custom data fields the customer may have
             * @param int $term_id
             * @param string $post_type (post|term)
             * @param array $meta
             **/
            do_action('wpil_meta_content_data_add_link', $term_id, 'term', $meta);
        }
    }

    /**
     * Get all Advanced Custom Fields names
     *
     * @return array
     */
    public static function getAdvancedCustomFieldsList($term_id)
    {
        global $wpdb;

        $fields = [];

        if(!class_exists('ACF') || get_option('wpil_disable_acf', false)){
            return $fields;
        }

        // get any ACF fields the user has ignored
        $ignored_fields = Wpil_Settings::getIgnoredACFFields();
        // get and ACF fields that the user wants to search
        $acf_fields = Wpil_Query::querySpecifiedAcfFields();

        $fields_query = $wpdb->get_results("SELECT SUBSTR(meta_key, 2) as `name` FROM {$wpdb->termmeta} WHERE term_id = $term_id AND meta_value LIKE 'field_%' AND SUBSTR(meta_key, 2) != '' {$acf_fields}");
        foreach ($fields_query as $field) {
            $name = trim($field->name);
            if(in_array($name, $ignored_fields, true)){
                continue;
            }

            if ($name) {
                $fields[] = $field->name;
            }
        }

        return $fields;
    }

    /**
     * Add link to the content in advanced custom fields
     *
     * @param $link
     * @param $post
     */
    public static function addLinkToAdvancedCustomFields($term_id)
    {
        // don't save the data if this is the result of using wp_update_post // there's no form submission, so $_POST will be empty
        if(empty($_POST)){
            return;
        }

        $meta = Wpil_Toolbox::get_encoded_term_meta($term_id, 'wpil_links', true);

        if (!empty($meta)) {
            foreach ($meta as $link) {
                $fields = self::getAdvancedCustomFieldsList($term_id);
                if (!empty($fields)) {
                    foreach ($fields as $field) {
                        if ($content = get_term_meta($term_id, $field, true)) {
                            if (is_string($content) && strpos($content, $link['sentence']) !== false) {
                                $changed_sentence = Wpil_Post::getSentenceWithAnchor($link);
                                $content = preg_replace('/' . preg_quote($link['sentence'], '/') . '/i', $changed_sentence, $content, 1);
                                update_term_meta($term_id, $field, $content);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Get term by slug with enhanced matching similar to get_post_id_from_any_url
     * Works for all registered taxonomies
     *
     * @param string $slug The slug to search for
     * @param string $url (Optional) The URL that we're trying to pull info from
     * @return WP_Term|false The found term or false if not found
     */
    public static function getTermBySlug($slug, $url = '')
    {
        global $wpdb, $wp_rewrite;

        // Basic validation
        if (empty($slug) || is_int($slug) || is_array($slug)) {
            return false;
        }

        // Get all public taxonomies
        $taxonomies = get_taxonomies(['public' => true]);
        if (empty($taxonomies)) {
            return false;
        }

        // clean up the slug just in case the permalink was ugly
        if(false !== strpos($slug, '=')){
            $bits = explode('=', trim($slug, '?'));
            $slug = end($bits);
        }

        // First, try to get the term directly by slug with taxonomy context from URL
        // If we have a URL, try to extract taxonomy context first
        if (!empty($url)) {
            $parsed_url = parse_url($url);
            $path = !empty($parsed_url['path']) ? trim($parsed_url['path'], '/') : '';

            if (!empty($path)) {
                // Try to match taxonomy base from the URL
                foreach ($taxonomies as $taxonomy) {
                    $tax_obj = get_taxonomy($taxonomy);
                    if (empty($tax_obj->rewrite['slug'])) {
                        continue;
                    }

                    $tax_slug = $tax_obj->rewrite['slug'];

                    // Check if the URL contains the taxonomy slug followed by our term slug
                    if (preg_match('#' . preg_quote($tax_slug, '#') . '/([^/]+)/?$#i', $path, $matches)) {
                        $found_slug = $matches[1];
                        if ($found_slug === $slug) {
                            $term = get_term_by('slug', $slug, $taxonomy);
                            if ($term && !is_wp_error($term)) {
                                return $term;
                            }
                        }
                    }
                }
            }
        }

        // If no term found by taxonomy context, try direct lookup
        $args = [
            'get'                   => 'all',
            'slug'                  => $slug,
            'taxonomy'              => array_values($taxonomies),
            'update_term_meta_cache'=> false,
            'orderby'               => 'none',
            'suppress_filter'       => true,
            'number'                => 100,
        ];

        $terms = get_terms($args);

        // If no terms found, try direct database query
        if (empty($terms) || is_wp_error($terms)) {
            $collation = "COLLATE utf8mb4_unicode_ci";
            $post_charset = 'utf8mb4';
            $table_data = $wpdb->get_row("SELECT table_name, table_collation, SUBSTRING_INDEX(table_collation, '_', 1) AS character_set FROM information_schema.tables WHERE table_schema = '{$wpdb->dbname}' AND table_name = '{$wpdb->posts}'");
            if(!empty($table_data) && isset($table_data->table_collation)){
                $collation = "COLLATE " . $table_data->table_collation;
                $post_charset = $table_data->character_set;
            }

            $terms = $wpdb->get_results($wpdb->prepare(
                "SELECT t.*, tt.* 
                FROM $wpdb->terms AS t 
                INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id 
                WHERE CONVERT(t.slug USING {$post_charset}) {$collation} = CONVERT(%s USING {$post_charset}) {$collation}
                AND tt.taxonomy IN ('" . implode("','", array_map('esc_sql', array_values($taxonomies))) . "')
                LIMIT 100",
                $slug
            ));

            if (empty($terms)) {
                return false;
            }
        }

        // If only one term found, return it
        if (count($terms) === 1) {
            return is_object($terms[0]) ? $terms[0] : (object)$terms[0];
        }

        // If we have a URL, try to match by URL structure
        if (!empty($url)) {
            $parsed_url = parse_url($url);
            $path = !empty($parsed_url['path']) ? trim($parsed_url['path'], '/') : '';

            if (!empty($path)) {
                // Try to find exact match first
                foreach ($terms as $term) {
                    $term = is_object($term) ? $term : (object)$term;
                    $term_link = get_term_link($term);

                    if (is_wp_error($term_link)) {
                        continue;
                    }

                    $term_path = trim(parse_url($term_link, PHP_URL_PATH), '/');
                    if ($term_path === $path) {
                        return $term;
                    }
                }

                // Try to find best match by path components
                $best_match = null;
                $best_score = 0;
                $path_parts = array_filter(explode('/', $path));

                foreach ($terms as $term) {
                    $term = is_object($term) ? $term : (object)$term;
                    $term_link = get_term_link($term);

                    if (is_wp_error($term_link)) {
                        continue;
                    }

                    $term_path = trim(parse_url($term_link, PHP_URL_PATH), '/');
                    $term_parts = array_filter(explode('/', $term_path));

                    // Calculate how many path components match
                    $matching_parts = 0;
                    $max_parts = min(count($path_parts), count($term_parts));

                    for ($i = 0; $i < $max_parts; $i++) {
                        if ($path_parts[$i] === $term_parts[$i]) {
                            $matching_parts++;
                        } else {
                            break;
                        }
                    }

                    // Calculate a score based on matching path components
                    $score = ($matching_parts / count($path_parts)) * 100;

                    if ($score > $best_score) {
                        $best_score = $score;
                        $best_match = $term;
                    }
                }

                if ($best_score > 50) { // Only return if we have a good match
                    return $best_match;
                }
            }
        }

        // If we have multiple terms but no URL, try to find the most likely one
        $preferred_taxonomies = ['category', 'post_tag', 'product_cat', 'product_tag'];
        foreach ($preferred_taxonomies as $tax) {
            foreach ($terms as $term) {
                $term = is_object($term) ? $term : (object)$term;
                if ($term->taxonomy === $tax) {
                    return $term;
                }
            }
        }

        // If all else fails, return the first term with the most posts
        usort($terms, function($a, $b) {
            $a_count = is_object($a) ? $a->count : 0;
            $b_count = is_object($b) ? $b->count : 0;
            return $b_count - $a_count;
        });

        return is_object($terms[0]) ? $terms[0] : (object)$terms[0];
    }

    /**
     * Gets all category terms for all active post types
     * 
     * @return array 
     **/
    public static function getAllCategoryTerms(){
        $post_types = Wpil_Settings::getPostTypes();
        if(empty($post_types)){
            return false;
        }

        $terms = get_transient('wpil_cached_category_terms');
        if(empty($terms)){

            $skip_terms = array(
                'product_type',
                'product_visibility',
                'product_shipping_class',
            );

            $terms = array();
            $term_ids = array();
            foreach($post_types as $type){
                $taxonomies = get_object_taxonomies($type);

                foreach($taxonomies as $taxonomy){
                    if(in_array($taxonomy, $skip_terms)){
                        continue;
                    }

                    $args = array(
                        'taxonomy' => $taxonomy,
                        'hide_empty' => false,
                        'number' => 10000,
                        'orderby' => 'count',
                        'order' => 'DESC'
                    );
                    $queried_terms = get_terms($args);

                    if(!is_a($terms, 'WP_Error')){
                        foreach($queried_terms as $term){
                            if(isset($term_ids[$term->term_id])){
                                continue;
                            }
                            $terms[] = $term;
                            $term_ids[$term->term_id] = true;
                        }
                    }
                }
            }

            // sort the terms to find the most used ones
            usort($terms, function($a, $b){
                if($a->count < $b->count){
                    return 1;
                }else if($a->count < $b->count){
                    return -1;
                }else{
                    return 0;
                }
            });

            // _only_ use the top 450 terms to save loading resources for sites that have many, many terms.
            $terms = array_slice($terms, 0, 450);

            // compress the terms to save space
            $terms_to_save = Wpil_Toolbox::compress($terms);

            // cache the terms for 5 minutes
            set_transient('wpil_cached_category_terms', $terms_to_save, MINUTE_IN_SECONDS * 5);
        }else{
            // if there are terms, decompress them
            $terms = Wpil_Toolbox::decompress($terms);
        }

        return $terms;
    }
}
