<?php

namespace SEOAIC;

use Exception;
use SEOAIC\DB\KeywordsPostsTable;
use SEOAIC\helpers\WPTransients;
use SEOAIC\keyword_types\KeywordHeadTermType;
use SEOAIC\keyword_types\KeywordLongTailTermType;
use SEOAIC\keyword_types\KeywordMidTailTermType;
use SEOAIC\last_used_prompts\KeywordsGenerateLastUsedPrompts;
use SEOAIC\relations\KeywordsPostsRelation;
use SeoaicAjaxValidation;

class SEOAIC_KEYWORDS
{
    private $seoaic;
    private const KEYWORDS_CACHE_KEY = 'seoaic_keywords';
    private const KEYWORDS_COUNT_CACHE_KEY = 'seoaic_all_keywords_count';
    private const KEYWORDS_CATEGORIES_KEY = 'keywords_categories';
    public const DAYS_COOLDOWN = 30;
    public const LAST_UPDATE_FIELD = 'keywords_last_update';
    // https://docs.dataforseo.com/v3/dataforseo_labs/google/historical_keyword_data/live/?bash
    public const SEARCH_VOLUME_PER_CHUNK = 600; // max 700 in DFS docs
    // https://docs.dataforseo.com/v3/dataforseo_labs/google/search_intent/live/?bash
    public const SEARCH_INTENT_PER_CHUNK = 800; // max 1000 in DFS docs

    public const KEYWORD_POST_TYPE = 'seoaic-keyword';

    public function __construct($_seoaic)
    {
        $this->seoaic = $_seoaic;

        add_action('wp_ajax_seoaic_get_keywords', [$this, 'getKeywordsAjax']);
        add_action('wp_ajax_seoaic_get_head_keywords', [$this, 'getHeadKeywordsAjax']);
        add_action('wp_ajax_seoaic_update_keywords', [$this, 'updateKeywordsAjax']);
        add_action('wp_ajax_seoaic_edit_generated_keyword', [$this, 'editGeneratedKeywordAjax']);
        add_action('wp_ajax_seoaic_generate_keywords_prompt', [$this, 'generateKeywordsAjax']);
        add_action('wp_ajax_seoaic_add_keyword', [$this, 'addKeywordAjax']);
        add_action('wp_ajax_seoaic_set_keyword_link', [$this, 'setKeywordLinkAjax']);
        add_action('wp_ajax_seoaic_remove_keyword', [$this, 'removeKeywordAjax']);
        add_action('wp_ajax_seoaic_remove_and_reassign_keyword', [$this, 'removeAndReassignKeywordAjax']);
        // add_action('wp_ajax_seoaic_get_keyword_serp', [$this, 'getKeywordSerp']);
        add_action('wp_ajax_seoaic_get_child_keywords', [$this, 'getChildKeywordsAjax']);
        add_action('wp_ajax_seoaic_keywords_get_siblings_keywords', [$this, 'getSiblingsKeywordsAjax']);
        add_action('wp_ajax_seoaic_keywords_category_add', [$this, 'categoryAddAjax']);
        add_action('wp_ajax_seoaic_keywords_get_categories', [$this, 'getKeywordsCategoriesAjax']);
        add_action('wp_ajax_seoaic_keywords_update_category', [$this, 'updateKeywordsCategory']);
        add_action('wp_ajax_seoaic_keywords_delete_category', [$this, 'deleteKeywordsCategory']);
        add_action('wp_ajax_seoaic_keywords_set_category', [$this, 'categorySetAjax']);
        add_action('wp_ajax_seoaic_keywords_poll_rank_data', [$this, 'getRankBulkAjax']);
        add_action('wp_ajax_seoaic_keyword_get_created_ideas', [$this, 'getCreatedIdeasAjax']);
        add_action('wp_ajax_seoaic_keyword_get_created_posts', [$this, 'getCreatedPostsAjax']);
        add_action('wp_ajax_seoaic_keywords_track_keyword', [$this, 'trackKeyword']);
        add_action('wp_ajax_seoaic_keywords_get_competitor_keywords', [$this, 'getCompetitorKeywords']);
        add_action('wp_ajax_seoaic_keywords_get_competitor_keywords_tabs', [$this, 'getCompetitorKeywordsTabsAjax']);
        add_action('wp_ajax_seoaic_set_keyword_location', [$this, 'setKeywordLocation']);
        add_action('wp_ajax_seoaic_migrate_terms_to_keywords', [$this, 'migrateTermsToKeywordsAjax']);
        add_action('wp_ajax_seoaic_get_keyword_internal_links_all', [$this, 'getKeywordInternalLinksAllAjax']);
        add_action('wp_ajax_seoaic_get_keyword_internal_links_recommended', [$this, 'getKeywordInternalLinksRecommendedAjax']);
        add_action('wp_ajax_seoaic_get_internal_links_random', [$this, 'getInternalLinksRandomAjax']);

        add_action('init', [$this, 'doUpdates'], 0);
        add_action('admin_init', [$this, 'createRelationTable'], 0);
        add_action('init', [$this, 'registerCategoriesTaxonomy'], 0);

        add_action('admin_notices', [$this, 'adminNoticeKeywordsPostsRelationMigrationNeeded']);
    }

    public function doUpdates()
    {
        if (
            !empty($_POST['seoaic_db_update_kw_posts_relation_table'])
            && 1 == $_POST['seoaic_db_update_kw_posts_relation_table']
        ) {
            if ((KeywordsPostsTable::getInstance())->doNewTableNameMigration()) {
                add_action('admin_notices', [$this, 'adminNoticeUpdateSuccess']);
            } else {
                add_action('admin_notices', [$this, 'adminNoticeUpdateFail']);
            }
        }
    }

    public function adminNoticeKeywordsPostsRelationMigrationNeeded()
    {
        if (
            !is_multisite()
            && KeywordsPostsTable::isNewTableNameMigrationNeeded()
        ) {
            ?>
            <div class="notice notice-warning">
                <h3><?php esc_html_e('Database update required!', 'seoaic');?></h3>
                <p><?php esc_html_e("We noticed that your SEOAI database is outdated. Please update it by clicking the button below.", 'seoaic');?></p>
                <p><?php esc_html_e("Don't forget to make database backup before doing any actions, just to make sure your data will not be lost if something goes wrong.", 'seoaic');?></p>
                <form method="POST" action="">
                    <input type="hidden" name="seoaic_db_update_kw_posts_relation_table" value="1">
                    <input type="submit" class="button action" value="<?php esc_html_e('Update Database', 'seoaic');?>">
                </form>
                <p></p>
            </div>
            <?php
        }
    }

    public function adminNoticeUpdateSuccess()
    {
        ?>
        <div class="notice notice-success is-dismissible">
            <p><?php esc_html_e("SEOAI database updated!", 'seoaic');?></p>
        </div>
        <?php
    }

    public function adminNoticeUpdateFail()
    {
        ?>
        <div class="notice notice-error is-dismissible">
            <p><?php esc_html_e("SEOAI database not updated!", 'seoaic');?></p>
        </div>
        <?php
    }

    public function createRelationTable()
    {
        if (is_multisite()) {
            KeywordsPostsTable::dropOldIfExists();
        }
        KeywordsPostsTable::createIfNotExists();
    }

    public function registerCategoriesTaxonomy()
    {
        register_taxonomy(self::KEYWORDS_CATEGORIES_KEY, self::KEYWORD_POST_TYPE, [
            'public' => false,
            'rewrite' => false,
        ]);
    }

    private static function makeKeywordSlug($string)
    {
        return preg_replace('/\s+/', '_', strtolower($string));
    }

    public function makeKeywordTypesOptions()
    {
        $html = '';
        $html .= (new KeywordHeadTermType())->makeOptionTag();
        $html .= (new KeywordMidTailTermType())->makeOptionTag();
        $html .= (new KeywordLongTailTermType())->makeOptionTag();

        return $html;
    }

    public function makeKeywordTypesRadios()
    {
        $html = '';
        $html .= (new KeywordHeadTermType())->makeRadioTag(true);
        $html .= (new KeywordMidTailTermType())->makeRadioTag();
        $html .= (new KeywordLongTailTermType())->makeRadioTag();

        return $html;
    }

    private function isHeadTermType($string = '')
    {
        return (new KeywordHeadTermType())->getName() === $string;
    }

    private function isMidTailTermType($string = '')
    {
        return (new KeywordMidTailTermType())->getName() === $string;
    }

    private function isLongTailTermType($string = '')
    {
        return (new KeywordLongTailTermType())->getName() === $string;
    }

    private function addKeyword($name = '', $parentID = 0)
    {
        if (empty(trim($name))) {
            return false;
        }

        if (!is_numeric($parentID)) {
            $parentID = intval($parentID);
        }

        $id = wp_insert_post([
            'post_title'    => $name,
            'post_type'     => self::KEYWORD_POST_TYPE,
            'post_name'     => self::makeKeywordSlug($name),
            'post_parent'   => $parentID,
        ]);

        if (is_wp_error($id)) {
            return false;
        }

        return $id;
    }

    /**
     * Set Keyword type
     * @param int $id Keyword ID
     * @param string $type Type: head term, mid-tail term, long-tail term
     */
    private function setKeywordType($id, $type): void
    {
        $this->updateKeywordData($id, ['keyword_type' => $type]);
    }

    /**
     * Update Keyword's link
     * @param int $id
     * @param string $link
     */
    private function setKeywordLink($id, $link): bool
    {
        return $this->updateKeywordData($id, ['page_link' => $link]);
    }

    public function convertKeywordsToPosts()
    {
        global $SEOAIC_OPTIONS;

        $keywordsConverted = !empty($SEOAIC_OPTIONS['keywords_converted_to_posts']) ? $SEOAIC_OPTIONS['keywords_converted_to_posts'] : 0;

        if (1 != $keywordsConverted) {
            $keywords = !empty($SEOAIC_OPTIONS['keywords']) ? $SEOAIC_OPTIONS['keywords'] : [];
            foreach ($keywords as $keyword) {
                if ($id = $this->addKeyword($keyword['name'])) {
                    if (isset($keyword['search_volume'])) {
                        update_post_meta($id, 'search_volume', $keyword['search_volume']);
                    }
                    if (isset($keyword['competition'])) {
                        update_post_meta($id, 'competition', $keyword['competition']);
                    }
                    if (isset($keyword['cpc'])) {
                        update_post_meta($id, 'cpc', $keyword['cpc']);
                    }
                    if (isset($keyword['location'])) {
                        update_post_meta($id, 'location', $keyword['location']);
                    }
                    if (isset($keyword['serp']['data'])) {
                        update_post_meta($id, 'serp_data', $keyword['serp']['data']);
                    }
                    if (isset($keyword['serp']['last_update'])) {
                        update_post_meta($id, 'serp_last_update', $keyword['serp']['last_update']);
                    }
                }
            }

            $this->seoaic->set_option('keywords_converted_to_posts', 1);
        }
    }

    private static function normalizeKeywordsFields($keywords = [])
    {
        if (
            !empty($keywords)
            && is_array($keywords)
        ) {
            foreach ($keywords as &$keyword) {
                if (!empty($keyword['rank_data'])) {
                    $keyword['rank_data'] = maybe_unserialize($keyword['rank_data']);
                }
                if (!empty($keyword['rank_history'])) {
                    $keyword['rank_history'] = maybe_unserialize($keyword['rank_history']);
                }
            }
        }

        return $keywords;
    }

    public function getKeywords()
    {
        global $wpdb;

        if ($keywords = WPTransients::getCachedValue(self::KEYWORDS_CACHE_KEY)) {
            return $keywords;
        }
        $keywordsPostsTable_safe = esc_sql((KeywordsPostsTable::getInstance())->tableName);

        $query = "SELECT
            p.ID as id,
            p.post_title as name,
            p.post_name as slug,
            p.post_parent as parent_id,
            search_volume__meta.meta_value as search_volume,
            keyword_type__meta.meta_value as keyword_type,
            competition__meta.meta_value as competition,
            cpc__meta.meta_value as cpc,
            rank__meta.meta_value as rank_data,
            rank_history__meta.meta_value as rank_history,
            rank_last_update__meta.meta_value as rank_last_update,
            rank_request_status__meta.meta_value as rank_request_status,
            search_intent_label__meta.meta_value as search_intent_label,
            location__meta.meta_value as location,
            language__meta.meta_value as lang,
            serp__meta.meta_value as serp_data,
            serp_last_update__meta.meta_value as serp_last_update,
            is_tracked__meta.meta_value as is_tracked,
            page_link__meta.meta_value as page_link,
            page_link_table.post_title as page_link_title,
            (
                SELECT GROUP_CONCAT(kp_relation.post_id)
                FROM {$keywordsPostsTable_safe} kp_relation
                LEFT JOIN {$wpdb->prefix}posts p2 on p2.ID = kp_relation.post_id
                WHERE kp_relation.keyword_id = p.ID AND p2.post_type = %s AND p2.post_status = %s
                GROUP BY kp_relation.keyword_id
            ) as ideas_created,
            (
                SELECT GROUP_CONCAT(kp_relation.post_id)
                FROM {$keywordsPostsTable_safe} kp_relation
                LEFT JOIN {$wpdb->prefix}posts p2 on p2.ID = kp_relation.post_id
                WHERE kp_relation.keyword_id = p.ID AND p2.post_type != %s AND p2.post_status != %s AND p2.post_status != 'trash'
                GROUP BY kp_relation.keyword_id
            ) as posts_created
        FROM {$wpdb->prefix}posts p
        LEFT JOIN {$wpdb->prefix}postmeta search_volume__meta on p.ID=search_volume__meta.post_id and search_volume__meta.meta_key='search_volume'
        LEFT JOIN {$wpdb->prefix}postmeta competition__meta on p.ID=competition__meta.post_id and competition__meta.meta_key='competition'
        LEFT JOIN {$wpdb->prefix}postmeta cpc__meta on p.ID=cpc__meta.post_id and cpc__meta.meta_key='cpc'
        LEFT JOIN {$wpdb->prefix}postmeta rank_history__meta on p.ID=rank_history__meta.post_id and rank_history__meta.meta_key='rank_history'
        LEFT JOIN {$wpdb->prefix}postmeta rank__meta on p.ID=rank__meta.post_id and rank__meta.meta_key='rank_data'
        LEFT JOIN {$wpdb->prefix}postmeta rank_last_update__meta on p.ID=rank_last_update__meta.post_id and rank_last_update__meta.meta_key='rank_last_update'
        LEFT JOIN {$wpdb->prefix}postmeta rank_request_status__meta on p.ID=rank_request_status__meta.post_id and rank_request_status__meta.meta_key='rank_request_status'
        LEFT JOIN {$wpdb->prefix}postmeta location__meta on p.ID=location__meta.post_id and location__meta.meta_key='location'
        LEFT JOIN {$wpdb->prefix}postmeta language__meta on p.ID=language__meta.post_id and language__meta.meta_key='language'
        LEFT JOIN {$wpdb->prefix}postmeta search_intent_label__meta on p.ID=search_intent_label__meta.post_id and search_intent_label__meta.meta_key='search_intent_label'
        LEFT JOIN {$wpdb->prefix}postmeta serp__meta on p.ID=serp__meta.post_id and serp__meta.meta_key='serp_data'
        LEFT JOIN {$wpdb->prefix}postmeta serp_last_update__meta on p.ID=serp_last_update__meta.post_id and serp_last_update__meta.meta_key='serp_last_update'
        LEFT JOIN {$wpdb->prefix}postmeta keyword_type__meta on p.ID=keyword_type__meta.post_id and keyword_type__meta.meta_key='keyword_type'
        LEFT JOIN {$wpdb->prefix}postmeta page_link__meta on p.ID=page_link__meta.post_id and page_link__meta.meta_key='page_link'
        LEFT JOIN {$wpdb->prefix}posts page_link_table on page_link_table.ID=page_link__meta.meta_value and page_link__meta.meta_key='page_link'
        LEFT JOIN {$wpdb->prefix}postmeta is_tracked__meta on p.ID=is_tracked__meta.post_id and is_tracked__meta.meta_key='is_tracked'
        WHERE p.post_type=%s";

        $preparedQuery = $wpdb->prepare($query, [
            SEOAIC_IDEAS::IDEA_TYPE,
            SEOAIC_IDEAS::IDEA_STATUS,
            SEOAIC_IDEAS::IDEA_TYPE,
            SEOAIC_IDEAS::IDEA_STATUS,
            self::KEYWORD_POST_TYPE,
        ]);
        $keywords = $wpdb->get_results($preparedQuery, ARRAY_A);
        $keywords = self::normalizeKeywordsFields($keywords);

        WPTransients::cacheValue(self::KEYWORDS_CACHE_KEY, $keywords, 30);

        return $keywords;
    }

    public function getAllKeywordsCount()
    {
        global $wpdb;

        if ($keywordsCount = WPTransients::getCachedValue(self::KEYWORDS_COUNT_CACHE_KEY)) {
            return $keywordsCount;
        }

        $query = "
            SELECT COUNT(p.ID) as count
            FROM {$wpdb->prefix}posts p
            WHERE p.post_type = %s
        ";

        $preparedQuery = $wpdb->prepare($query, [
            self::KEYWORD_POST_TYPE
        ]);

        $keywordsCount = $wpdb->get_var($preparedQuery);

        WPTransients::cacheValue(self::KEYWORDS_COUNT_CACHE_KEY, $keywordsCount, 30);

        return (int) $keywordsCount;
    }

    public function getKeywordByID($id)
    {
        if (!is_numeric($id)) {
            return false;
        }

        $keyword = $this->getKeywordsByIDs([$id]);
        if (
            $keyword
            && !empty($keyword[0])
        ) {
            return $keyword[0];
        }

        return false;
    }

    /**
     * Get Keywords by IDs
     * @param array $ids
     * @return array
     */
    public function getKeywordsByIDs($ids = [])
    {
        global $wpdb;

        if (empty($ids)) {
            return false;
        } elseif (
            !is_array($ids)
            && is_numeric($ids)
        ) {
            $ids = [$ids];
        }

        $idsCount = count($ids);
        $idsPlaceholders = implode(', ', array_fill(0, $idsCount, '%d'));

        $keywordsPostsTable_safe = esc_sql((KeywordsPostsTable::getInstance())->tableName);

        $query = "SELECT
            p.ID as id,
            p.post_title as name,
            p.post_name as slug,
            p.post_parent as parent_id,
            keyword_type__meta.meta_value as keyword_type,
            search_volume__meta.meta_value as search_volume,
            competition__meta.meta_value as competition,
            cpc__meta.meta_value as cpc,
            rank__meta.meta_value as rank_data,
            rank_history__meta.meta_value as rank_history,
            rank_last_update__meta.meta_value as rank_last_update,
            rank_request_status__meta.meta_value as rank_request_status,
            search_intent_label__meta.meta_value as search_intent_label,
            location__meta.meta_value as location,
            language__meta.meta_value as lang,
            serp__meta.meta_value as serp_data,
            serp_last_update__meta.meta_value as serp_last_update,
            is_tracked__meta.meta_value as is_tracked,
            page_link__meta.meta_value as page_link,
            page_link_table.post_title as page_link_title,
            (
                SELECT GROUP_CONCAT(kp_relation.post_id)
                FROM {$keywordsPostsTable_safe} kp_relation
                LEFT JOIN {$wpdb->prefix}posts p2 on p2.ID = kp_relation.post_id
                WHERE kp_relation.keyword_id = p.ID AND p2.post_type = %s AND p2.post_status = %s
                GROUP BY kp_relation.keyword_id
            ) as ideas_created,
            (
                SELECT GROUP_CONCAT(kp_relation.post_id)
                FROM {$keywordsPostsTable_safe} kp_relation
                LEFT JOIN {$wpdb->prefix}posts p2 on p2.ID = kp_relation.post_id
                WHERE kp_relation.keyword_id = p.ID AND p2.post_type != %s AND p2.post_status != %s AND p2.post_status != 'trash'
                GROUP BY kp_relation.keyword_id
            ) as posts_created
        FROM {$wpdb->prefix}posts p
        LEFT JOIN {$wpdb->prefix}postmeta search_volume__meta on p.ID=search_volume__meta.post_id and search_volume__meta.meta_key='search_volume'
        LEFT JOIN {$wpdb->prefix}postmeta competition__meta on p.ID=competition__meta.post_id and competition__meta.meta_key='competition'
        LEFT JOIN {$wpdb->prefix}postmeta cpc__meta on p.ID=cpc__meta.post_id and cpc__meta.meta_key='cpc'
        LEFT JOIN {$wpdb->prefix}postmeta rank_history__meta on p.ID=rank_history__meta.post_id and rank_history__meta.meta_key='rank_history'
        LEFT JOIN {$wpdb->prefix}postmeta rank__meta on p.ID=rank__meta.post_id and rank__meta.meta_key='rank_data'
        LEFT JOIN {$wpdb->prefix}postmeta rank_last_update__meta on p.ID=rank_last_update__meta.post_id and rank_last_update__meta.meta_key='rank_last_update'
        LEFT JOIN {$wpdb->prefix}postmeta rank_request_status__meta on p.ID=rank_request_status__meta.post_id and rank_request_status__meta.meta_key='rank_request_status'
        LEFT JOIN {$wpdb->prefix}postmeta location__meta on p.ID=location__meta.post_id and location__meta.meta_key='location'
        LEFT JOIN {$wpdb->prefix}postmeta language__meta on p.ID=language__meta.post_id and language__meta.meta_key='language'
        LEFT JOIN {$wpdb->prefix}postmeta search_intent_label__meta on p.ID=search_intent_label__meta.post_id and search_intent_label__meta.meta_key='search_intent_label'
        LEFT JOIN {$wpdb->prefix}postmeta serp__meta on p.ID=serp__meta.post_id and serp__meta.meta_key='serp_data'
        LEFT JOIN {$wpdb->prefix}postmeta serp_last_update__meta on p.ID=serp_last_update__meta.post_id and serp_last_update__meta.meta_key='serp_last_update'
        LEFT JOIN {$wpdb->prefix}postmeta keyword_type__meta on p.ID=keyword_type__meta.post_id and keyword_type__meta.meta_key='keyword_type'
        LEFT JOIN {$wpdb->prefix}postmeta page_link__meta on p.ID=page_link__meta.post_id and page_link__meta.meta_key='page_link'
        LEFT JOIN {$wpdb->prefix}posts page_link_table on page_link_table.ID=page_link__meta.meta_value and page_link__meta.meta_key='page_link'
        LEFT JOIN {$wpdb->prefix}postmeta is_tracked__meta on p.ID=is_tracked__meta.post_id and is_tracked__meta.meta_key='is_tracked'
        WHERE p.post_type=%s
        AND p.ID in (" . $idsPlaceholders . ")";

        $preparedQuery = $wpdb->prepare($query, array_merge(
            [SEOAIC_IDEAS::IDEA_TYPE],
            [SEOAIC_IDEAS::IDEA_STATUS],
            [SEOAIC_IDEAS::IDEA_TYPE],
            [SEOAIC_IDEAS::IDEA_STATUS],
            [self::KEYWORD_POST_TYPE],
            $ids
        ));
        $keywords = $wpdb->get_results($preparedQuery, ARRAY_A);

        return self::normalizeKeywordsFields($keywords);
    }

    /**
     * Converts from WP_Post object or array into Keywords array
     */
    public function convertFormatFromPostsToKeywords($keywordPosts = [])
    {
        $keywords = [];

        if (!empty($keywordPosts)) {
            $keywordsIds = array_map(function ($item) {
                if (is_array($item)) {
                    return $item['ID'];
                }
                if (is_object($item)) {
                    return $item->ID;
                }
                return '';
            }, $keywordPosts);
            $keywords = $this->getKeywordsByIDs($keywordsIds);
        }

        return $keywords;
    }

    /**
     * Gets Keywords by type.
     * @param object $typeObject instanse of Key
     */
    public function getKeywordsByType($typeObject = null)
    {
        $result = [];

        if (
            is_null($typeObject)
            || !is_a($typeObject, 'SEOAIC\keyword_types\KeywordBaseType')
        ) {
            error_log(' '.print_r('! SEOAIC\keyword_types\KeywordBaseType', true));
            return $result;
        }

        $keywords = $this->getKeywords();
        $isHeadTermType = $this->isHeadTermType($typeObject->getName());

        foreach ($keywords as $keyword) {
            if (
                $keyword['keyword_type'] == $typeObject->getName()
                || (
                    $isHeadTermType
                    && is_null($keyword['keyword_type'])
                )
            ) {
                $result[] = $keyword;
            }
        }

        return $result;
    }

    public function getKeywordsAjax () {
        $request = wp_unslash($_REQUEST);
        $term    = isset($request['term']) ? sanitize_text_field(trim($request['term'])) : '';

        $keywords = $this->getKeywords();

        if ($term !== '') {
            $keywords = array_filter($keywords, function ($keyword) use ($term) {
                return !empty($keyword['name']) && stripos($keyword['name'], $term) !== false;
            });

            $keywords = array_values($keywords);
        }

        SEOAICAjaxResponse::success()->addFields($keywords)->wpSend();
    }

    public function getHeadKeywordsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $headTermKeywords = $this->getKeywordsByType(new KeywordHeadTermType());
        $fields = [
            'keywords' => $headTermKeywords,
        ];

        if (
            isset($_REQUEST['options_html'])
            && 1 == $_REQUEST['options_html']
        ) {
            $fields['options_html'] = $this->makeKeywordsOptionsTags($headTermKeywords);
        }

        SEOAICAjaxResponse::success()->addFields($fields)->wpSend();
    }

    public function getChildKeywordsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_REQUEST['id'])
            || !is_numeric($_REQUEST['id'])
        ) {
            SEOAICAjaxResponse::error('Keyword not selected!')->wpSend();
        }

        $keywords = $this->getChildKeywordsByParentID($_REQUEST['id']);
        $fields = [
            'back_action' => !empty($_REQUEST['back_action']) ? $_REQUEST['back_action'] : '',
            'keywords' => $keywords,
        ];
        if (
            isset($_REQUEST['options_html'])
            && 1 == $_REQUEST['options_html']
        ) {
            $fields['options_html'] = $this->makeKeywordsOptionsTags($keywords);
        }

        SEOAICAjaxResponse::success()->addFields($fields)->wpSend();
    }

    public function getSiblingsKeywordsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_REQUEST['keyword_id'])
            || !is_numeric($_REQUEST['keyword_id'])
        ) {
            SEOAICAjaxResponse::error('Keyword not selected!')->wpSend();
        }

        $currentKeyword = $this->getKeywordByID($_REQUEST['keyword_id']);

        $childKeywords = $this->getChildKeywordsByParentID($currentKeyword['parent_id']);
        $filteredChildKeywords = array_filter($childKeywords, function ($item) use ($currentKeyword) {
            return $item['id'] != $currentKeyword['id'];
        });

        $returnArray = array_map(function ($item) {
            return [
                'id'    => $item['id'],
                'name'  => $item['name'],
            ];
        }, $filteredChildKeywords);

        $fields = [
            'keywords' => $returnArray,
        ];

        if (
            isset($_REQUEST['options_html'])
            && 1 == $_REQUEST['options_html']
        ) {
            $fields['options_html'] = $this->makeKeywordsOptionsTags($filteredChildKeywords);
        }

        SEOAICAjaxResponse::success()->addFields($fields)->wpSend();
    }

    /**
     * Get child keywords by ID
     */
    public function getChildKeywordsByParentID($id = null)
    {
        if (
            is_null($id)
            || !is_numeric($id)
        ) {
            return [];
        }

        $args = [
            'post_type' => self::KEYWORD_POST_TYPE,
            'post_status' => 'any',
            'post_parent' => $id,
            'posts_per_page' => -1,
            'fields' => 'ids'
        ];

        $childKeywordsIDs = get_posts($args);
        $childKeywords = $this->getKeywordsByIDs($childKeywordsIDs);

        return $childKeywords ? $childKeywords : [];
    }

    public static function makeKeywordsOptionsTags($keywords = []): string
    {
        $html = '';

        if (
            !empty($keywords)
            && is_array($keywords)
        ) {
            foreach ($keywords as $keyword) {
                $html .= '<option value="' . esc_attr($keyword['id']) . '">' . esc_html($keyword['name']) . '</option>';
            }

        } else {
            $html .= '<option value="" disabled>Nothing found</option>';
        }

        return $html;
    }

    /**
     * Updates Keyword's data (meta fields)
     * @param array|int $keyword Keyword or ID
     * @param array $data meta fields to update. Assoc array in a "key => value" format
     * @return bool
     */
    private function updateKeywordData($keyword, $data = []): bool
    {
        if (empty($data)) {
            return false;
        }

        if (
            is_numeric($keyword)
            && (int) $keyword == $keyword
        ) {
            $id = $keyword;
        } else {
            $id = $keyword['id'];
        }

        // $updateRes = wp_update_post([
        //     'ID'            => $id,
        //     'meta_input'    => $data,
        // ]);

        // if (is_wp_error($updateRes)) {
        //     return false;
        // }

        $clearCacheFlag = false;

        foreach ($data as $field => $newValue) {
            if (
                !isset($keyword[$field])
                || (
                    isset($keyword[$field])
                    && $keyword[$field] != $newValue
                )
            ) {
                update_post_meta($id, $field, $newValue);
                $clearCacheFlag = true;
            }
        }

        if ($clearCacheFlag) {
            WPTransients::deleteCachedValue(self::KEYWORDS_CACHE_KEY);
        }

        return true;
    }

    /**
     * Updates Keyword's data (WP post)
     * @param array|int $keyword Keyword or ID
     * @param array $data WP post fields to update. Assoc array in a "key => value" format
     * @return bool
     */
    private function updateKeywordPost($keyword, $data = [])
    {
        if (empty($data)) {
            return false;
        }

        if (
            is_numeric($keyword)
            && (int) $keyword == $keyword
        ) {
            $id = $keyword;
        } else {
            $id = $keyword['id'];
        }

        $data['ID'] = $id;
        $updateRes = wp_update_post($data);

        if (is_wp_error($updateRes)) {
            return false;
        }

        WPTransients::deleteCachedValue(self::KEYWORDS_CACHE_KEY);

        return true;
    }

    /**
     * Generate keywords prompt
     */
    public function generateKeywordsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $n = !empty($_REQUEST['keywords_count']) ? $_REQUEST['keywords_count'] : 1;
        $prompt = !empty($_REQUEST['keywords_prompt']) ? $_REQUEST['keywords_prompt'] : '';
        $type = !empty($_REQUEST['keyword_type'][0]) ? $_REQUEST['keyword_type'][0] : '';
        $formData = [
            'head_term_id'  => !empty($_REQUEST['head_term_id']) ? intval($_REQUEST['head_term_id']) : '',
            'mid_tail_id'   => !empty($_REQUEST['mid_tail_id']) ? intval($_REQUEST['mid_tail_id']) : '',
        ];

        (new KeywordsGenerateLastUsedPrompts())->store($prompt);

        $keywords = $this->generate($n, $prompt, $type, true, $formData);

        if (
            $this->isHeadTermType($type)
            && !empty($_REQUEST['seoaic_keywords_categories'])
            && !empty($keywords)
        ) {
            $categoryID = $this->maybeAddCategory($_REQUEST['seoaic_keywords_categories']);

            foreach ($keywords as $kw) {
                $this->setKeywordCategory($kw['id'], $categoryID);
            }
        }

        $keywordsIDs = array_map(function ($item) {
            return $item['id'];
        }, $keywords);


        // SEOAICAjaxResponse::alert('updated')->addFields([
        //     'content' => [
        //         'content' => $this->makeKeywordsTableMarkup($keywords),
        //     ],
        // ])->wpSend();

        $message = '<div class="mb-19">' . esc_html__("New keywords have been generated:", "seoaic") . '</div>';
        $message .= self::makeGeneratedKeywordsRows($keywords);
        $button = '<div class="additional-button"><div class="position-absolute seoaic-delete-all-ideas-button-wrapper">
            <button title="' . esc_html__("Delete all", "seoaic") . '" type="button"
                class="button button-danger-hover seoaic-delete-all-ideas-button modal-button confirm-modal-button"
                data-posts-ids="' . esc_attr(implode(',', $keywordsIDs)) . '"
                data-modal="#seoaic-confirm-modal"
                data-action="seoaic_remove_keyword"
                data-content="' . esc_html__("Do you want to remove all these keywords?", "seoaic") . '"
                data-form-callback="keywords_popup_remove_keywords"
            >' . esc_html__("Delete all", "seoaic") . '</button>
        </div></div>';

        SEOAICAjaxResponse::alert($message)->addFields(['button' => $button])->wpSend();
    }

    private static function makeGeneratedKeywordsRows($keywords = []): string
    {
        if (empty($keywords)) {
            return '';
        }

        ob_start();
        foreach ($keywords as $keywords) {
            ?>
            <div
                class="mb-10 alert-added-ideas item-row-id-<?php echo esc_attr($keywords['id']);?>"
            >
                <b class="num-col">#<?php echo esc_html($keywords['id']);?></b>
                <div class="title-col">
                    <input type="text" name="idea_updated_title" value="<?php echo esc_attr($keywords['name']);?>" class="seoaic-form-item idea-updated-title">
                    <span class="idea-orig-title"><?php echo esc_html($keywords['name']);?></span>
                </div>
                <div class="btn1-col">
                    <span class="save idea-btn dashicons dashicons-yes" title="<?php esc_attr_e("Save", "seoaic");?>" data-action="seoaic_edit_generated_keyword" data-post-id="<?php echo esc_attr($keywords['id']);?>"></span>
                    <span class="edit idea-btn" title="<?php esc_attr_e("Edit", "seoaic");?>"></span>
                </div>
                <div class="btn2-col">
                    <span class="delete idea-btn dashicons dashicons-dismiss" title="<?php esc_attr_e("Delete", "seoaic");?>" data-action="seoaic_remove_keyword" data-post-id="<?php echo esc_attr($keywords['id']);?>"></span>
                </div>
            </div>
            <?php
        }

        return ob_get_clean();
    }

    private static function makeIdeasGenerateButton($ideas=[])
    {
        ob_start();
        foreach ($ideas as $key => $value) {
            ?>
            <label data-id="label-idea-mass-create-<?php echo esc_attr($key);?>">
                <input type="checkbox" checked="" class="seoaic-form-item" name="idea-mass-create" value="<?php echo esc_attr($key);?>"> <b>#<?php echo esc_html($key);?></b> - <span><?php echo esc_html($value);?></span>
            </label>
            <?php
        }
        return ob_get_clean();
    }

    /**
     * Ajax action - edit (update)
     */
    public function editGeneratedKeywordAjax($args = [])
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $__REQUEST = wp_unslash($_REQUEST);
        $defaults = [
            'id'    => $__REQUEST['item_id'] ?? 0,
            'name'  => $__REQUEST['item_name'] ?? '',
        ];
        $parsedArgs = wp_parse_args($args, $defaults);

        if (
            empty($parsedArgs['name'])
            || empty($parsedArgs['id'])
            || !is_numeric($parsedArgs['id'])
        ) {
            SEOAICAjaxResponse::error('Forbidden')->wpSend();
        }

        $title = stripslashes(sanitize_text_field($parsedArgs['name']));
        $id = intval($parsedArgs['id']);

        $result = $this->updateKeywordPost($id, [
            'post_title' => $title,
        ]);

        if ($result) {
            SEOAICAjaxResponse::success('Keyword #' . esc_html($id) . ' updated!')->addFields([
                'id' => $id,
            ])->wpSend();
        }

        SEOAICAjaxResponse::error('Keyword #' . esc_html($id) . ' not updated!')->wpSend();
    }

    private function maybeAddCategory($category)
    {
        if (!is_numeric($category)) { // dynamically added category, add it first
            return $this->categoryAddIfNotExists($category);
        }

        return (int)$category;
    }

    /**
     * Generate keywords
     * @param int $n count of keywords to be generated
     * @param string $prompt prompt
     * @param string $type Keyword type (head, mid-tail, long-tail term). Optional. Head term by default
     * @param bool $return_new return only new generated or all keywords
     */
    public function generate($n, $prompt = '', $type = null, $return_new = false, $formData = [])
    {
        global $SEOAIC_OPTIONS;

        $parentPostId = 0;
        $generatedKeywordsIDs = [];
        $generatedKeywordsArray = [];
        $type = !empty($type) ? $type : (new KeywordHeadTermType())->getName();
        $location = sanitize_text_field($_REQUEST['location']);
        $language = sanitize_text_field($_REQUEST['language']);
        // $currentKeywords = $this->getKeywords();
        // $currentKeywordsNames = array_map(function ($kw) {
        //     return $kw['name'];
        // }, $currentKeywords);

        // $data = [
        //     'title'         => SEOAIC_SETTINGS::getBusinessName(),
        //     'language'      => $language,
        //     'description'   => SEOAIC_SETTINGS::getBusinessDescription(),
        //     'industry'      => SEOAIC_SETTINGS::getIndustry(),
        //     'content'       => $prompt,
        //     'domain'        => get_bloginfo('url'),
        //     'prompt'        => $prompt,
        //     'n'             => intval($n),
        //     'current'       => implode(', ', $currentKeywordsNames),
        // ];

        // $keywordsAddResult = $this->seoaic->curl->init('api/ai/keywords', $data, true, false, true);

        if ($this->isHeadTermType($type)) {
            $generatedKeywordsArray = $this->generateHeadTermKeywords($language, $prompt, intval($n));

        } elseif ($this->isMidTailTermType($type)) {
            $formData['location'] = $location;
            $formData['language'] = $language;
            $formData['limit'] = $n;
            $generatedKeywordsArray = $this->generateMidTailTermKeywords($formData);
            $parentPostId = !empty($_REQUEST['head_term_id']) ? intval($_REQUEST['head_term_id']) : 0;

        } elseif ($this->isLongTailTermType($type)) {
            $formData['location'] = $location;
            $formData['language'] = $language;
            $formData['limit'] = $n;
            $generatedKeywordsArray = $this->generateLongTailTermKeywords($formData);
            $parentPostId = !empty($_REQUEST['mid_tail_id']) ? intval($_REQUEST['mid_tail_id']) : 0;
        }

        if (empty($generatedKeywordsArray)) {
            SEOAICAjaxResponse::error('No unique Keywords were generated!')->wpSend();
        }

        foreach ($generatedKeywordsArray as $keyword) {
            if ($id = $this->addKeyword($keyword, $parentPostId)) {
                $generatedKeywordsIDs[] = $id;
                $this->setKeywordType($id, $type);
            }
        }

        $newKeywords = $this->getKeywordsByIDs($generatedKeywordsIDs);

        $fieldsData = [
            'keywords' => $newKeywords,
            'location' => $location,
            'language' => $language,
            'search_volumes' => [],
            'search_intents' => [],
        ];

        $data = [
            'keywords' => $generatedKeywordsArray,
            'location' => $location,
            'language' => $language,
            'mode' => 'auto',
            'email' => $SEOAIC_OPTIONS['seoaic_api_email'],
        ];

        if (!empty($generatedKeywordsArray)) {
            if ($kwSearchVolumeResult = $this->requestSearchVolumes($data)) {
                $fieldsData['search_volumes'] = $kwSearchVolumeResult['data'];
            }

            if ($keywordsSearchIntents = $this->requestSearchIntent($data, $newKeywords)) {
                $fieldsData['search_intents'] = $keywordsSearchIntents;
            }

            $this->queueRank($data, $newKeywords);
            // if (false !== $keywordsRank) {
            //     $fieldsData['rank'] = $keywordsRank;
            // }

            $this->updateKeywordsFields($fieldsData);
        }

        WPTransients::deleteCachedValue(self::KEYWORDS_CACHE_KEY);

        if ($return_new) {
            if (empty($newKeywords)) {
                return [];
            }

            return $this->getKeywordsByIDs($generatedKeywordsIDs);
        }

        return $this->getKeywords();
    }

    /**
     * Makes request to backend (ChatGPT) to generate Head Term keywords
     * @param string $language
     * @param string $prompt
     * @param int $n number of posts to generate. Default 1
     * @return array
     */
    private function generateHeadTermKeywords($language, $prompt = '', $n = 1): array
    {
        $currentKeywords = $this->getKeywords();
        $currentKeywordsNames = array_map(function ($kw) {
            return $kw['name'];
        }, $currentKeywords);

        $prompt = wp_unslash($prompt);

        $data = [
            'title'         => SEOAIC_SETTINGS::getBusinessName(),
            'language'      => $language,
            'description'   => SEOAIC_SETTINGS::getBusinessDescription(),
            'industry'      => SEOAIC_SETTINGS::getIndustry(),
            'content'       => $prompt,
            'domain'        => get_bloginfo('url'),
            'prompt'        => $prompt,
            'n'             => intval($n),
            'current'       => implode(', ', $currentKeywordsNames),
        ];

        $keywordsAddResult = $this->seoaic->curl->init('api/ai/keywords', $data, true, false, true);
        if (
            empty($keywordsAddResult['status'])
            || $keywordsAddResult['status'] !== 'success'
            || empty($keywordsAddResult['content'])
        ) {
            SEOAICAjaxResponse::alert('Keywords not generated!')->wpSend();
        }

        $skipped_keywords = $this->removeDuplicatedKeywordsFromInput($keywordsAddResult['content'], $currentKeywords);
        $generatedKeywordsArray = $keywordsAddResult['content'];

        return $generatedKeywordsArray;
    }

    private function generateMidTailTermKeywords($formData = [])
    {
        global $SEOAIC_OPTIONS;

        $limit = !empty($formData['limit']) ? $formData['limit'] : 1;
        $data = [
            'location'  => !empty($formData['location']) ? $formData['location'] : '',
            'language'  => !empty($formData['language']) ? $formData['language'] : '',
            // 'limit'     => $limit,
            'limit'     => 8, // max available related keywords for 0 level from DataForSEO
            'keyword'   => '',
            'email'     => $SEOAIC_OPTIONS['seoaic_api_email'],
        ];

        if (
            !empty($formData['head_term_id'])
            && is_numeric($formData['head_term_id'])
        ) {
            $keyword = $this->getKeywordByID($formData['head_term_id']);

            if (!empty($keyword)) {
                $data['keyword'] = $keyword['name'];
            }
        }

        foreach ($data as $key => $value) {
            if (empty($value)) {
                SEOAICAjaxResponse::error('Empty required field "' . $key . '"');
            }
        }

        // $keywordsAddResult = $this->seoaic->curl->init('/api/ai/keyword-suggestions', $data, true, false, true);
        $keywordsAddResult = $this->seoaic->curl->init('/api/ai/related-keywords', $data, true, false, true);

        if (
            empty($keywordsAddResult['status'])
            || $keywordsAddResult['status'] !== 'success'
            || empty($keywordsAddResult['data'])
        ) {
            SEOAICAjaxResponse::alert('Keywords not generated!')->wpSend();
        }

        // $generatedKeywordsArray = array_map(function ($item) {
        //     return $item['keyword'];
        // }, $keywordsAddResult['data']);
        $generatedKeywordsArray = $keywordsAddResult['data'];

        $currentKeywords = $this->getKeywords();
        $skipped_keywords = $this->removeDuplicatedKeywordsFromInput($generatedKeywordsArray, $currentKeywords);

        if (count($generatedKeywordsArray) > $limit) {
            $generatedKeywordsArray = array_slice($generatedKeywordsArray, 0, $limit);
        }

        return $generatedKeywordsArray;
    }

    private function generateLongTailTermKeywords($formData = [])
    {
        global $SEOAIC_OPTIONS;

        $data = [
            'location'  => !empty($formData['location']) ? $formData['location'] : '',
            'language'  => !empty($formData['language']) ? $formData['language'] : '',
            'limit'     => !empty($formData['limit']) ? $formData['limit'] : 1,
            'keyword'   => '',
            'email'     => $SEOAIC_OPTIONS['seoaic_api_email'],
        ];

        if (
            !empty($formData['head_term_id'])
            && is_numeric($formData['head_term_id'])
        ) {
            $keyword = $this->getKeywordByID($formData['mid_tail_id']);

            if (!empty($keyword)) {
                $data['keyword'] = $keyword['name'];
            }
        }

        foreach ($data as $key => $value) {
            if (empty($value)) {
                SEOAICAjaxResponse::error('Empty required field "' . $key . '"');
            }
        }

        $keywordsAddResult = $this->seoaic->curl->init('/api/ai/related-keywords', $data, true, false, true);

        if (
            empty($keywordsAddResult['status'])
            || $keywordsAddResult['status'] !== 'success'
            || empty($keywordsAddResult['data'])
        ) {
            SEOAICAjaxResponse::alert('Keywords not generated!')->wpSend();
        }

        $generatedKeywordsArray = $keywordsAddResult['data'];

        $currentKeywords = $this->getKeywords();
        $skipped_keywords = $this->removeDuplicatedKeywordsFromInput($generatedKeywordsArray, $currentKeywords);

        return $generatedKeywordsArray;
    }

    public static function getLastUpdateTimestamp()
    {
        global $SEOAIC_OPTIONS;

        return !empty($SEOAIC_OPTIONS[self::LAST_UPDATE_FIELD]) ? intval($SEOAIC_OPTIONS[self::LAST_UPDATE_FIELD]) : 0;
    }

    public static function getNextUpdateTimestamp()
    {
        return self::getLastUpdateTimestamp() + self::DAYS_COOLDOWN * DAY_IN_SECONDS;
    }

    /**
     * Get statistic for keywords and returns it with keywords
     * @param bool $manual
     * @param array $keywords optional field. Gets statistics for all keywords if no data are provided
     * @return array
     */
    public function generateKeywordsStat(bool $manual, array $keywords = [])
    {
        global $SEOAIC_OPTIONS;

        $keywords = !empty($keywords) ? $keywords : $this->getKeywords();
        $ids = array_map(function ($kw) {
            return $kw['id'];
        }, $keywords);

        if (empty($keywords)) {
            return [];
        }

        $mode = $manual ? 'manual' : 'auto';
        // $keywordsStatUpdateManual = !empty($SEOAIC_OPTIONS['keywords_stat_update_manual']) ? $SEOAIC_OPTIONS['keywords_stat_update_manual'] : 0;
        // $keywordsStatUpdate = !empty($SEOAIC_OPTIONS['keywords_stat_update']) ? $SEOAIC_OPTIONS['keywords_stat_update'] : 0;
        // $update = $manual ? $keywordsStatUpdateManual : $keywordsStatUpdate;

        $keywords = array_map(function ($kw) {
            if (empty($kw['lang'])) {
                $firstLang = $this->seoaic->multilang->getFirstLanguageByLocationName($kw['location']);

                if (is_array($firstLang) && !empty($firstLang['name'])) {
                    $kw['lang'] = $firstLang['name'];
                }
            }
            return $kw;
        }, $keywords);

        $requestData = [
            'keywords' => $keywords,
            'location' => '',
            'language' => '',
            'mode' => $mode,
            'email' => $SEOAIC_OPTIONS['seoaic_api_email'],
        ];

        if (
            empty($SEOAIC_OPTIONS[self::LAST_UPDATE_FIELD])
            || time() > self::getNextUpdateTimestamp()
        ) {
            $fieldsData = [
                'keywords' => $keywords,
                'search_volumes' => [],
                'search_intents' => [],
            ];

            $fieldsData['search_volumes'] = $this->getSearchVolumes($requestData, $manual);
            $fieldsData['search_intents'] = $this->getSearchIntents($requestData);

            $this->updateKeywordsFields($fieldsData);
            $this->queueRank($requestData, $keywords);

            $SEOAIC_OPTIONS[self::LAST_UPDATE_FIELD] = time();
            update_option('seoaic_options', $SEOAIC_OPTIONS);
        }

        return $this->getKeywordsByIDs($ids);
    }

    private function updateKeywordsFields($fieldsData = [])
    {
        foreach ($fieldsData['keywords'] as $keyword) {
            $keywordNameLower = strtolower($keyword['name']);
            $newData = [
                'location' => isset($fieldsData['location']) ? $fieldsData['location'] : (!empty($keyword['location']) ? $keyword['location'] : ''),
                'language' => isset($fieldsData['language']) ? $fieldsData['language'] : (!empty($keyword['lang']) ? $keyword['lang'] : ''),
            ];

            if (
                !empty($fieldsData['search_volumes'])
                && is_array($fieldsData['search_volumes'])
            ) {
                foreach ($fieldsData['search_volumes'] as $keywordSearchVolume) {
                    if ($keywordNameLower !== strtolower($keywordSearchVolume['keyword'])) {
                        continue;
                    }

                    $newData['search_volume'] = !empty($keywordSearchVolume['search_volume']) ? $keywordSearchVolume['search_volume'] : '';
                    $newData['competition'] = !empty($keywordSearchVolume['competition']) ? $keywordSearchVolume['competition'] : '';
                    $newData['cpc'] = !empty($keywordSearchVolume['cpc']) ? $keywordSearchVolume['cpc'] : '';
                    break;
                }
            }

            if (
                !empty($fieldsData['search_intents'])
                && is_array($fieldsData['search_intents'])
                && array_key_exists($keywordNameLower, $fieldsData['search_intents'])
            ) {
                $newData['search_intent_label'] = $fieldsData['search_intents'][$keywordNameLower]['label'];
            }

            // if (
            //     !empty($fieldsData['rank'][$keyword['name']])
            // ) {
            //     $keywordRankData = $fieldsData['rank'][$keyword['name']];

            //     if (!empty($keywordRankData['positions'])) {
            //         $newData['rank_data'] = $keywordRankData['positions'];
            //     }

            //     if (!empty($keywordRankData['rank_last_update'])) {
            //         $newData['rank_last_update'] = $keywordRankData['rank_last_update'];
            //     }
            // }

            $this->updateKeywordData($keyword, $newData);
        }
    }

    /**
     * Update keywords
     */
    public function updateKeywordsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $nextTimestamp = self::getNextUpdateTimestamp();
        $nextDate = date('d/m/Y H:i', $nextTimestamp);

        if (time() < $nextTimestamp) {
            SEOAICAjaxResponse::alert(esc_html__("Next refresh available only on ", "seoaic") . $nextDate)->wpSend();
        }

        $keywords = $this->getKeywords();
        $keywords = $this->generateKeywordsStat(true, $keywords);

        SEOAICAjaxResponse::success('updated')->addFields([
            'content' => [
                'content'   => $this->makeKeywordsTableMarkup($keywords),
                'notify'    => esc_html__('Keywords have been updated. The next refresh is available on ') . esc_html($nextDate),
            ],
        ])->wpSend();
    }

    /**
     * Add keywords
     */
    public function addKeywordAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        global $SEOAIC_OPTIONS;

        $__REQUEST = wp_unslash($_REQUEST);

        if (empty($_REQUEST['item_name'])) {
            SEOAICAjaxResponse::error('Value can`t be empty!')->wpSend();
        }
        if (
            empty($_REQUEST['location'])
            || empty($_REQUEST['language'])
        ) {
            SEOAICAjaxResponse::error('Location/Language can`t be empty!')->wpSend();
        }

        // $location = SEOAIC_SETTINGS::getLocation();
        // $language = SEOAIC_SETTINGS::getLanguage();
        $location = sanitize_text_field($__REQUEST['location']);
        $language = sanitize_text_field($__REQUEST['language']);

        $keywordsNamesArray = preg_split('/\r\n|[\r\n]/', stripslashes(sanitize_textarea_field($_REQUEST['item_name'])));
        $keywordsNamesArray = array_map('trim', $keywordsNamesArray);
        $keywordsNamesArray = array_filter($keywordsNamesArray);
        $keywordsNamesArray = array_unique($keywordsNamesArray);

        $skippedKeywords = $this->removeDuplicatedKeywordsFromInput(
            $keywordsNamesArray,
            [], // all keywords
            [
                'location' => $location,
                'lang' => $language,
            ]
        );

        if (empty($keywordsNamesArray)) {
            SEOAICAjaxResponse::error('No unique Keywords found or Keywords already exist!')->wpSend();
        }


        $ids = [];
        $addedKeywordsNames = [];
        $parentPostId = 0;
        $keywordType = !empty($_REQUEST['keyword_type'][0]) ? $_REQUEST['keyword_type'][0] : '';
        $categoryID = null;
        $keywords_categories = $_REQUEST['seoaic_keywords_categories'];
        $custom_keyword_category = !empty($_REQUEST['seoaic_custom_keyword_category']) ? $_REQUEST['seoaic_custom_keyword_category'] : ''; // when keyword explorer questions to ideas

        if (
            empty($keywords_categories)
            && !empty($custom_keyword_category)
        ) {
            $keywords_categories = $custom_keyword_category;
        }

        if ($this->isMidTailTermType($keywordType)) {
            if (
                empty($_REQUEST['head_term_id'])
                || !is_numeric($_REQUEST['head_term_id'])
                || intval($_REQUEST['head_term_id']) != $_REQUEST['head_term_id']
            ) {
                SEOAICAjaxResponse::error('No Head Term selected!')->wpSend();
            }

            $parentPostId = intval($_REQUEST['head_term_id']);

        } elseif ($this->isLongTailTermType($keywordType)) {
            if (
                empty($_REQUEST['mid_tail_id'])
                || !is_numeric($_REQUEST['mid_tail_id'])
                || intval($_REQUEST['mid_tail_id']) != $_REQUEST['mid_tail_id']
            ) {
                SEOAICAjaxResponse::error('No Mid-Tail Term selected!')->wpSend();
            }

            $parentPostId = intval($_REQUEST['mid_tail_id']);
        }

        if (
            $this->isHeadTermType($keywordType)
            && !empty($keywords_categories)
        ) {
            $categoryID = $this->maybeAddCategory($keywords_categories);
        }

        foreach ($keywordsNamesArray as $keywordName) {
            if ($id = $this->addKeyword($keywordName, $parentPostId)) {
                $ids[] = $id;
                $addedKeywordsNames[] = strtolower($keywordName);
                $this->setKeywordType($id, $keywordType);

                if ($this->isHeadTermType($keywordType)) {
                    $this->setKeywordCategory($id, $categoryID);
                }
            }
        }

        $addedKeywords = $this->getKeywordsByIDs($ids);

        if (!$addedKeywords) {
            SEOAICAjaxResponse::error('No keywords were added!')->wpSend();
        }

        $fieldsData = [
            'keywords' => $addedKeywords,
            'location' => $location,
            'language' => $language,
            'search_volumes' => [],
            'search_intents' => [],
        ];

        $data = [
            'keywords' => $keywordsNamesArray,
            'location' => $location,
            'language' => $language,
            'mode' => 'auto',
            'email' => $SEOAIC_OPTIONS['seoaic_api_email'],
        ];

        if ($kwSearchVolumeResult = $this->requestSearchVolumes($data)) {
            $fieldsData['search_volumes'] = $kwSearchVolumeResult['data'];
        }

        if ($keywordsSearchIntents = $this->requestSearchIntent($data, $addedKeywords)) {
            $fieldsData['search_intents'] = $keywordsSearchIntents;
        }

        $this->queueRank($data, $addedKeywords);
        // if (false !== $keywordsRank) {
        //     $fieldsData['rank'] = $keywordsRank;
        // }

        $this->updateKeywordsFields($fieldsData);

        $message = [];
        if ($addedKeywordsNames) {
            $message[] = 'Keywords «' . implode(", ", $addedKeywordsNames) . '» have been added!';
        }
        if ($skippedKeywords) {
            $message[] = 'Keywords «' . implode(", ", $skippedKeywords) . '» have been skipped (duplicates)!';
        }

        WPTransients::deleteCachedValue(self::KEYWORDS_CACHE_KEY);

        SEOAICAjaxResponse::alert(implode('<br />', $message))->wpSend();
    }

    public function removeKeywordAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $__REQUEST = wp_unslash($_REQUEST);

        if (
            empty($__REQUEST['item_id'])
            && empty($__REQUEST['item_ids'])
        ) {
            SEOAICAjaxResponse::error('Value can`t be empty!')->wpSend();
        }

        $ids = !empty($__REQUEST['item_id']) ? $__REQUEST['item_id'] : $__REQUEST['item_ids'];
        $deletedKeywords = [];
        $selectedKeywordsIDs = array_filter(explode(',', stripslashes(sanitize_text_field($ids))), function ($item) {
            return is_numeric($item);
        });
        $selectedKeywords = $this->getKeywordsByIDs($selectedKeywordsIDs);

        foreach ($selectedKeywords as $_keyword) {
            if ($this->removeKeyword($_keyword)) {
                $deletedKeywords[] = $_keyword['name'];
                $deletedKeywordsIDs[] = $_keyword['id'];
            }
        }

        if (!empty($deletedKeywords)) {
            SEOAICAjaxResponse::success('Keywords «' . implode(', ', $deletedKeywords) . '» has been deleted!')->addFields([
                'content' => [
                    'item_type' => 'keyword',
                    'removed_ids' => $deletedKeywordsIDs,
                ],
            ])->wpSend();
        }

        SEOAICAjaxResponse::error('Keywords do not exist!')->wpSend();
    }

    public function removeAndReassignKeywordAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $removeSubKeywords = !empty($_REQUEST['remove_sub_items']);
        if (
            (empty($_REQUEST['delete_keyword_id'])
            || !is_numeric($_REQUEST['delete_keyword_id'])
            || empty($_REQUEST['reassign_keyword_id'])
            || !is_numeric($_REQUEST['reassign_keyword_id']))
            && !$removeSubKeywords
        ) {
            SEOAICAjaxResponse::error('Value can`t be empty!')->wpSend();
        }

        $errMsgs = [];
        $deletedKeywords = [];
        $selectedKeywordsIDs = [$_REQUEST['delete_keyword_id']];

        $reassignKeyword = $removeSubKeywords
            ? ['id' => -1]
            : $this->getKeywordByID($_REQUEST['reassign_keyword_id']);
        if (empty($reassignKeyword['id'])) {
            SEOAICAjaxResponse::error('Keyword not found!')->wpSend();
        }

        foreach ($selectedKeywordsIDs as $selectedKeywordID) {
            $selectedKeyword = $this->getKeywordByID($selectedKeywordID);

            if (!empty($selectedKeyword['id'])) {
                $childItems = $this->getChildKeywordsByParentID($selectedKeyword['id']);

                if (!empty($childItems)) {
                    foreach ($childItems as $child_item) {
                        $this->removeOrReassignChildKeywords(
                                $child_item,
                                $reassignKeyword,
                                $errMsgs,
                                $deletedKeywords,
                                $removeSubKeywords
                        );
                    }

                    if (empty($errMsgs)) {
                        if ($this->removeKeyword($selectedKeyword)) {
                            $deletedKeywords[] = $selectedKeyword['name'];
                        }
                    } else {
                        SEOAICAjaxResponse::alert('Errors: '.implode(' ', $errMsgs) . ' Deletion was rejected!');
                    }
                }
            }
        }

        if (!empty($deletedKeywords)) {
            SEOAICAjaxResponse::alert('Keywords «' . implode(', ', $deletedKeywords) . '» has been deleted!')->wpSend();
        }

        SEOAICAjaxResponse::error('Keywords do not exist!')->wpSend();
    }

    /**
     * Removes or reassign child keywords
     *
     * @param array $childItem
     * @param array $reassignKeyword
     * @param array $errMsgs
     * @param array $deletedKeywords
     * @param bool $remove
     * @return void
     */
    private function removeOrReassignChildKeywords(
            array $childItem,
            array $reassignKeyword,
            array &$errMsgs,
            array &$deletedKeywords,
            bool $remove
    ): void
    {
        if ($remove) {
            $childSubItem = $this->getChildKeywordsByParentID($childItem['id']);
            $allItems = [$childItem, ...$childSubItem];
            foreach ($allItems as $item) {
                if ($this->removeKeyword($item)) {
                    $deletedKeywords[] = $item['name'];
                }
            }

            return;
        }

        $result = wp_update_post([
            'ID'            => $childItem['id'],
            'post_parent'   => $reassignKeyword['id'],
        ]);

        if (is_wp_error($result)) {
            $errMsgs[] = $result->get_error_message();
        } elseif (0 == $result) {
            $errMsgs[] = 'Keyword "' . $childItem['name'] . '" not reassigned!';
        }
    }

    private function removeKeyword($keyword)
    {
        if (
            !empty($keyword)
            && !empty($keyword['id'])
        ) {
            $result = wp_delete_post($keyword['id'], true);

            if ($result) {
                KeywordsPostsRelation::deleteByKeywordID($keyword['id']);
            }

            return $result;
        }

        return false;
    }

    /**
     * Returns sanitized keywords
     * @param array $keywords array of keywords to be sanitized. If array is empty function gets all keywords from database and sanitizes it.
     * @return array
     */
    public function sanitizeKeywords($keywords = [])
    {
        if (
            empty($keywords)
            || !is_array($keywords)
        ) {
            $keywords = $this->getKeywords();
        }

        if (empty($keywords)) {
            return [];
        }

        $returnKeywords = [];

        foreach ($keywords as $keyword) {
            if (!trim($keyword['name'])) {
                $this->removeKeyword($keyword);
            } else {
                $returnKeywords[] = $keyword;
            }
        }

        return $returnKeywords;
    }

    private function isKeywordNameExists(string $keywordName, array $currentKeywords = []): bool
    {
        $currentKeywords = !empty($currentKeywords) ? $currentKeywords : $this->getKeywords();

        foreach ($currentKeywords as $k) {
            if (strtolower($keywordName) === strtolower($k['name'])) {
                return true;
            }
        }

        return false;
    }

    private function isKeywordExists(string $keywordName, array $currentKeywords = [], array $additionalParamsToCompare = []): bool
    {
        $currentKeywords = !empty($currentKeywords) ? $currentKeywords : $this->getKeywords();
        $exist = false;

        if (empty($additionalParamsToCompare)) {
            return $this->isKeywordNameExists($keywordName, $currentKeywords);
        }

        foreach ($currentKeywords as $k) {
            $paramsAreEqual = true;

            if (strtolower($k['name']) != strtolower($keywordName)) {
                continue;
            }

            foreach ($additionalParamsToCompare as $paramKey => $paramValue) {
                if (
                    !empty($k[$paramKey])
                    && $k[$paramKey] != $paramValue
                ) {
                    $paramsAreEqual = false;
                }
            }

            if ($paramsAreEqual) {
                $exist = true;
                break;
            }
        }

        return $exist;
    }

    /**
     * Removes existing keywords from input. Modifies the provided array of keywords.
     * @param array $keywords Array of Keyword names
     * @param array @currentKeywords Optional. List of Keywords to compare with. If not set - compares with all Keywords
     * @param array $additionalParamsToCompare Optional. Array of fields that will be compared in addition to the name. If not set - compares by 'name' field only
     */
    private function removeDuplicatedKeywordsFromInput(&$keywords, $currentKeywords = [], $additionalParamsToCompare = [])
    {
        $cleanedKeywords = [];

        foreach ($keywords as $i => $keyword) {
            if (!empty($additionalParamsToCompare)) { // check by name and additional param
                if ($this->isKeywordExists($keyword, $currentKeywords, $additionalParamsToCompare)) {
                    $cleanedKeywords[] = $keyword;
                    unset($keywords[$i]);
                }

            } else { // check names only
                if ($this->isKeywordNameExists($keyword, $currentKeywords)) {
                    $cleanedKeywords[] = $keyword;
                    unset($keywords[$i]);
                }
            }
        }

        $keywords = array_values($keywords); // fix indexes after duplicates removal

        return $cleanedKeywords;
    }

    private function makeKeywordsHierarchy($keywords = [])
    {
        $result = [];
        $headTermKeywords = array_filter($keywords, function ($item) {
            return $this->isHeadTermType($item['keyword_type']) || empty($item['parent_id']);
        });
        foreach ($headTermKeywords as &$keyword) {
            $this->makeChildKeywords($keyword, $keywords);
            $result[] = $keyword;
        }

        return $result;
    }

    private function makeChildKeywords(&$parent, $keywords)
    {
        $parent['child_items'] = [];
        foreach ($keywords as $keyword) {
            if ($parent['id'] == $keyword['parent_id']) {
                $parent['child_items'][] = $keyword;
            }
        }

        if (empty($parent['child_items'])) {
            return;
        } else {
            foreach ($parent['child_items'] as &$child_item) {
                $this->makeChildKeywords($child_item, $keywords);
            }
        }
    }

    private function sortKeywordsByNums(&$keywords, $field, $order = "desc")
    {
        usort($keywords, function ($a, $b) use ($field, $order) {
            $aVal = isset($a[$field]) ? (float)$a[$field] : 0;
            $bVal = isset($b[$field]) ? (float)$b[$field] : 0;

            if ($aVal === $bVal) return 0;
            return ($order === "desc") ? ($aVal < $bVal ? 1 : -1) : ($aVal > $bVal ? 1 : -1);
        });

        foreach ($keywords as &$keyword) {
            if (!empty($keyword['child_items']) && is_array($keyword['child_items'])) {
                $this->sortKeywordsByNums($keyword['child_items'], $field, $order); // рекурсивно
            }
        }
    }

    private function sortKeywordsByStrings(&$keywords, $field, $order = "desc")
    {
        usort($keywords, function ($a, $b) use ($field, $order) {
            $aVal = isset($a[$field]) ? (string)$a[$field] : '';
            $bVal = isset($b[$field]) ? (string)$b[$field] : '';

            return $order === "desc" ? strcmp($bVal, $aVal) : strcmp($aVal, $bVal);
        });

        foreach ($keywords as &$keyword) {
            if (!empty($keyword['child_items']) && is_array($keyword['child_items'])) {
                $this->sortKeywordsByStrings($keyword['child_items'], $field, $order); // рекурсивно
            }
        }
    }


	public function makeKeywordsTableMarkup($keywords = [])
    {
        $keywords = !empty($keywords) && is_array($keywords) ? $keywords : $this->getKeywords();
        $keywordsHierarchy = $this->makeKeywordsHierarchy($keywords);

        $mappingFields = [
            'keyword' => 'name',
            'difficulty' => 'competition',
            'search-vol' => 'search_volume',
            'cpc' => 'cpc',
        ];
        if (!empty($_GET['sortby'])) {
            $sortBy = $_GET['sortby'];
            $order = !empty($_GET['order'] && "asc" == strtolower($_GET['order'])) ? 'asc' : 'desc';
            $mappingField = array_key_exists($sortBy, $mappingFields) ? $mappingFields[$sortBy] : '';

            if (
                'keyword' === $sortBy
                || 'difficulty' === $sortBy
            ) {
                $this->sortKeywordsByStrings($keywordsHierarchy, $mappingField, $order);

            } elseif (
                'search-vol' === $sortBy
                || 'cpc' === $sortBy
            ) {
                $this->sortKeywordsByNums($keywordsHierarchy, $mappingField, $order);

            } else {
                $this->sortKeywordsByNums($keywordsHierarchy, 'search_volume');
            }

        } else {
            $this->sortKeywordsByNums($keywordsHierarchy, 'search_volume');
        }

        $html = '';

        foreach ($keywordsHierarchy as $keyword) {
            $html .= $this->makeKeywordRowRecursive($keyword);
        }

        return $html;
    }

    private function getCompetitors($keyword)
    {
        $serp_data = !isset($keyword['serp_data']) ? false : unserialize($keyword['serp_data']);
        $competitors = !isset($serp_data['added_competitors']) ? [] : $serp_data['added_competitors'];

        $html = '';
        if ($competitors) {
            $html .= '<ul>';
            foreach ($competitors as $competitor) {
                $html .= '<li><a href="#" class="modal-button" data-modal="#competitor-compare" data-position="' . esc_attr($competitor['position']) . '"><span>' . esc_html(str_replace("www.", "", $competitor['domain'])) . '</span></a><span class="pos"><i class="icon-step-posotion"></i>' . esc_html($competitor['position']) . '</span></li>';
            }
            $html .= '</ul>';
        }

        return $html;
    }

    private function getFilters()
    {
        return [
            's'         => $_GET['s'] ?? '',
            'cluster'   => $_GET['cluster'] ?? '',
            'intent'    => $_GET['intent'] ?? '',
            'filter'    => $_GET['filter'] ?? '',
        ];
    }

    private function isAnyFilterApplied()
    {
        $filters = $this->getFilters();
        if (!empty($filters)) {
            foreach ($filters as $filter) {
                if (!empty($filter)) {
                    return true;
                }
            }
        }

        return false;
    }

    private function makeFilteringHideClass($keyword, $filterParam = '')
    {
        $filters = $this->getFilters();
        if (
            !in_array($filterParam, array_keys($filters))
            || empty($filters[$filterParam])
        ) {
            return '';
        }

        if ('s' == $filterParam) {
            if (false === stripos($keyword['name'], $filters['s'])) {
                return ' s-hide';
            }

        } elseif ('cluster' == $filterParam) {
            $headTermTypeName = (new KeywordHeadTermType())->getName();
            // $midTailTermTypeName = (new KeywordMidTailTermType())->getName();
            // $longTailTermTypeName = (new KeywordLongTailTermType())->getName();
            $keywordType = !empty($keyword['keyword_type']) ? $keyword['keyword_type'] : $headTermTypeName;

            if (
                $headTermTypeName == $keywordType
                && '' != $filters['cluster']
            ) {
                $keywordsCategories = get_the_terms($keyword['id'], self::KEYWORDS_CATEGORIES_KEY);

                if (
                    (
                        '_without_cluster' != $filters['cluster']
                        && !$keywordsCategories
                    )
                    || (
                        $keywordsCategories
                        && $filters['cluster'] != $keywordsCategories[0]->term_id
                    )
                ) {
                    return ' cluster-hide';
                }
            }

        } elseif ('intent' == $filterParam) {
            if (
                !empty($filters['intent'])
                && $keyword['search_intent_label'] != $filters['intent']
            ) {
                return ' intent-hide';
            }

        } elseif ('filter' == $filterParam) {
            if (
                'tracked' == $filters['filter']
                && (
                    empty($keyword['is_tracked'])
                    || "false" == $keyword['is_tracked']
                )
            ) {
                return ' tracked-hide';

            } elseif (
                'ranked' == $filters['filter']
                && empty($keyword['rank_data'])
            ) {
                return ' ranked-hide';

            } elseif (
                'rank-changed' == $filters['filter']
                && (
                    (
                        empty($keyword['rank_data'])
                        && (
                            empty($keyword['rank_history'])
                            || empty(end($keyword['rank_history'])['rank'])
                        )
                    )
                    || (
                        !empty($keyword['rank_data'])
                        && !empty($keyword['rank_history'])
                        && !empty(end($keyword['rank_history'])['rank'])
                        && $this->getTopRank(end($keyword['rank_history'])['rank']) == $this->getTopRank($keyword['rank_data'])
                    )
                )
            ) {
                return ' rank-changed-hide';

            } elseif (
                'new-ranked' == $filters['filter']
                && (
                    ( // no current rank, rank changed from previous not empty rank
                        empty($keyword['rank_data'])
                        && !empty($keyword['rank_history'])
                        && !empty(end($keyword['rank_history'])['rank'])
                    )
                    || ( // no current rank, no previous rank
                        empty($keyword['rank_data'])
                        && (
                            empty($keyword['rank_history'])
                            || empty(end($keyword['rank_history'])['rank'])
                        )
                    )
                    || ( // rank exists, previous rank not empty
                        !empty($keyword['rank_data'])
                        && !empty($keyword['rank_history'])
                        && !empty(end($keyword['rank_history'])['rank'])
                    )
                )
            ) {
                return ' new-ranked-hide';

            }
        }

        return '';
    }

    private function also_ask_map_button($language_safe, $location_safe, $keyword, $keyword_id = 0)
    {
        return '<button
                    data-lang="' . $language_safe . '"
                    data-location="' . $location_safe . '"
                    data-keyword="' . esc_attr($keyword) . '"
                    data-keyword-id="' . esc_attr($keyword_id) . '"
                    class="modal-button seoaic-generate-also-ask-map generate-more-keywords keyword-generate-add-btn position-relative"
                    data-modal="#seoaic-also-ask-generate-ideas"
                    title="' . esc_attr__("Create Content map ", 'seoaic') . '">
                        <span class="dashicons dashicons-networking"></span>
                        <span class="generate-add-btns-text">' . esc_html__('Content map', 'seoaic') . '</span>
                </button>';
    }

    private function makeKeywordRowRecursive($keyword, $parentID = null)
    {
        $html = '';
        $isCompetitorsButtonDisabled = empty($keyword['search_volume']) || $keyword['search_volume'] < 100;
        $searchIntentLabel_safe = !empty($keyword['search_intent_label']) ? '<span class="search-intent-label search-intent-label-' . esc_attr($keyword['search_intent_label']) . '">' . esc_html__($keyword['search_intent_label']) . '</span>' : '-';
        $headTermTypeName = (new KeywordHeadTermType())->getName();
        $midTailTermTypeName = (new KeywordMidTailTermType())->getName();
        $longTailTermTypeName = (new KeywordLongTailTermType())->getName();
        $keywordType = !empty($keyword['keyword_type']) ? $keyword['keyword_type'] : $headTermTypeName;
        $dataIDAttr_safe = ' data-id="' . esc_attr($keyword['id']) . '"';
        $cssClass = !empty($keyword['child_items']) ? ' seoaic-has-children' : '';
        $SERPLastUpdate = $this->getSERPLastUpdate($keyword);
        // $SERPIconClass = $this->isSERPDataValid($keyword) ? 'dashicons-saved' : 'dashicons-visibility';
        $isTracked = !empty($keyword['is_tracked']) ? "true" == $keyword['is_tracked'] : false;
        $trackedClass = $isTracked ? ' seoaic-tracked-keyword' : '';
        $trackedStatus = $isTracked ? 'checked="checked"' : '';
        $language_safe = !empty($keyword['lang']) ? esc_attr($keyword['lang']) : '';
        $location_safe = !empty($keyword['location']) ? esc_attr($keyword['location']) : '';
        $ideasCount = 0;
        $postsCount = 0;

        if (!empty($keyword['ideas_created'])) {
            $ideasCount = count(explode(',', $keyword['ideas_created']));
        }
        if (!empty($keyword['posts_created'])) {
            $postsCount = count(explode(',', $keyword['posts_created']));
        }

        $searchHideClass_safe = $this->makeFilteringHideClass($keyword, 's');
        $clusterHideClass_safe = $this->makeFilteringHideClass($keyword, 'cluster');
        $intentHideClass_safe = $this->makeFilteringHideClass($keyword, 'intent');
        $filterHideClass_safe = $this->makeFilteringHideClass($keyword, 'filter');

        $rowClosedClass = $headTermTypeName == $keywordType && !$this->isAnyFilterApplied() ? ' seoaic-closed' : '';


        $html .= '
        <div class="row-line ' . esc_attr($cssClass) . ' ' . esc_attr($keywordType) . $clusterHideClass_safe . '"' . $dataIDAttr_safe . '>
            <div class="row-line-container' . $trackedClass . $intentHideClass_safe . $searchHideClass_safe . $filterHideClass_safe . '">
                <div class="seoaic-track-col">
                    <div class="seoaic-track">
                        <input type="checkbox"
                            ' . $trackedStatus . '
                            class="seoaic-track-key"
                            name="seoaic-track-key"
                            data-id="' . esc_attr($keyword['id']) . '"
                            id="kw_track_' . esc_attr($keyword['id']) . '"
                        >
                        <label for="kw_track_' . esc_attr($keyword['id']) . '">
                            <span class="dashicons dashicons-star-empty seoaic-track-btn" title="' . esc_html__('Track keyword', 'seoaic') . '"></span>
                            <span class="dashicons dashicons-star-filled seoaic-track-btn" title="' . esc_html__('Tracked keyword', 'seoaic') . '"></span>
                        </label>
                    </div>
                </div>

                <div class="check">
                    <input type="checkbox"
                        class="seoaic-check-key"
                        name="seoaic-check-key"
                        data-id="' . esc_attr($keyword['id']) . '"
                        data-keyword="' . esc_attr($keyword['slug']) . '"
                        data-has-children="' . (int)(!empty($keyword['child_items']) && is_array($keyword['child_items'])) . '"
                        data-keyword-type="' . esc_attr($keywordType) . '"
                    >
                </div>

                <div class="keyword' . $rowClosedClass . '">
                    <span data-attr="' . $midTailTermTypeName . ' ' . $headTermTypeName . ' ' . $keywordType . '">' . esc_html($keyword['name']) . '</span>';
                    if (
                        $headTermTypeName == $keywordType
                        || $midTailTermTypeName == $keywordType
                    ) {
                        $childType = $headTermTypeName == $keywordType ? $midTailTermTypeName : $longTailTermTypeName;
                        $btnTitle = ($headTermTypeName == $keywordType ? 'mid-tail' : 'long-tail') . ' terms';
                        $html .= '<div class="generate-add-btns-wrapper">
                            <button
                        data-lang="' . $language_safe . '"
                        data-location="' . $location_safe . '"
                        data-keyword="' . esc_html($keyword['name']) . '"
                        data-modal="#keywords-explorer-popup"
                        class="modal-button generate-more-keywords keyword-generate-add-btn position-relative" title="' . esc_html__("Generate more " . $btnTitle, 'seoaic') . '" data-for-id="' . esc_attr($keyword['id']) . '" data-for-parent="' . esc_attr($keyword['parent_id']) . '" data-type="' . esc_attr($childType) . '">
                                <span class="dashicons dashicons-admin-generic"></span>
                                <span class="generate-add-btns-text">' . esc_html__('Generate more', 'seoaic') . '</span>
                            </button>
                            <button class="add-more-keywords keyword-generate-add-btn position-relative" title="' . esc_html__("Add more " . $btnTitle, 'seoaic') . '" data-for-id="' . esc_attr($keyword['id']) . '" data-for-parent="' . esc_attr($keyword['parent_id']) . '" data-type="' . esc_attr($childType) . '">
                                <span class="dashicons dashicons-plus-alt2"></span>
                                <span class="generate-add-btns-text">' . esc_html__('Add more', 'seoaic') . '</span>
                            </button>
                            ' . $this->also_ask_map_button($language_safe, $location_safe, $keyword['name'], $keyword['id']) . '
                        </div>';
                    }
                    if ($longTailTermTypeName == $keywordType)
                    {
                        $html .= '<div class="generate-add-btns-wrapper">' . $this->also_ask_map_button($language_safe, $location_safe, $keyword['name'], $keyword['id']) . '</div>';
                    }
                $html .= '</div>';
        if ($headTermTypeName == $keywordType) {
            $keywordsCategories = get_the_terms($keyword['id'], self::KEYWORDS_CATEGORIES_KEY);

            $html .= '
                <div class="category">';
            if (false === $keywordsCategories) {
                $html .= '
                    <button class="add-keyword-category modal-button"
                        data-modal="#keywords-set-category-modal"
                        data-action="seoaic_keyword_set_category"
                        data-mode="set-category"
                    >+ ' . esc_html__('set cluster', 'seoaic') . '
                        <div class="dn edit-form-items">
                            <input type="hidden" name="keyword_id" value="' . esc_attr($keyword['id']) . '">
                        </div>
                    </button>';
            } else {
                $category_id = $keywordsCategories[0]->term_id;
                $remote_id = get_term_meta($category_id, 'remote_cluster_id', true);
                $html .= '
                    <button title="' . esc_html__('Change Cluster', 'seoaic') . '"
                        class="update-keyword-category modal-button"
                        data-modal="#keywords-set-category-modal"
                        data-action="seoaic_keyword_set_category"
                        data-category-id="' . esc_attr($category_id) . '"
                        data-remote-cluster-id="' . esc_attr($remote_id) . '"
                        data-mode="set-category"
                    ><span>' . esc_html($keywordsCategories[0]->name) . '</span>
                        <div class="dn edit-form-items">
                            <input type="hidden" name="keyword_id" value="' . esc_attr($keyword['id']) . '">
                        </div>
                    </button>';
            }
            $html .= '
                </div>';
        }

        $hasRankClass = !empty($keyword['rank_data']) ? " has-rank-value" : '';

        $html .= '
                <div class="search-vol">' . (!empty($keyword['search_volume']) ? esc_attr($keyword['search_volume']) : '-') . '</div>

                <div class="difficulty' . (!empty($keyword['competition']) ? ' ' . esc_attr($keyword['competition']) : '') . '">' . (!empty($keyword['competition']) ? esc_attr($keyword['competition']) : '-') . '</div>

                <div class="cpc">' . (!empty($keyword['cpc']) ? '€' . esc_attr($keyword['cpc']) : '-') . '</div>

                <div class="rank text-center keyword-' . esc_attr($keyword['id']) . '-rank ' . esc_attr($hasRankClass) . ' position-relative">';
        if ('queued' == $keyword['rank_request_status']) {
            $html .= '<div class="queued w-100"></div>';
        } else {
            $rankHistory = !empty($keyword['rank_history']) ? $keyword['rank_history'] : [];
            $html .= self::makeDisplayRankValue($keyword['rank_data'], $keyword['rank_last_update'], $rankHistory);
        }

//        <span class="vertical-align-middle dashicons ' . $SERPIconClass . '"></span>
//        <span class="serp-link-label">' . esc_html__('+ Show competitors', 'seoaic') . '</span>
        $linkTitle = !empty($keyword['page_link_title']) ? $keyword['page_link_title'] : (!is_numeric($keyword['page_link']) ? $keyword['page_link'] : '');
        $html .= '
                </div>

                <div class="serp competitors">
                ' . $this->getCompetitors($keyword) . '
                    <button title="' . esc_html__('Last update', 'seoaic') . ': ' . esc_html($SERPLastUpdate) . '"
                            data-title="' . esc_html__('Competitors', 'seoaic') . '" type="button" ' . ($isCompetitorsButtonDisabled ? 'disabled' : '') . '
                            class="button-primary outline modal-button"
                            data-modal="#add-competitors"
                            data-action="seoaic_get_search_term_competitors"
                            data-id="' . esc_attr($keyword['id']) . '"
                            data-keyword="' . esc_attr($keyword['slug']) . '"
                    >' . esc_html__('+ show competitors', 'seoaic') . '
                    </button>
                </div>

                <div class="search-intent">' . $searchIntentLabel_safe . '</div>

                <div class="created-posts">';
                    if (0 == $ideasCount) {
                        $html .= esc_html__('Ideas', 'seoaic') . ': ' . esc_html($ideasCount) . '<br>';
                    } else {
                        $html .= '
                        <div
                            title="' . esc_html__("Show Ideas", "seoaic") . '"
                            class="modal-button mb-5px seoaic-cursor-pointer"
                            data-modal="#keywords-show-created-modal"
                            data-action="seoaic_keyword_get_created_ideas"
                            data-id="' . esc_attr($keyword['id']) . '"
                            data-modal-title="' . esc_html__('Created Ideas', 'seoaic') . '"
                        >' . esc_html__('Ideas', 'seoaic') . ': ' . esc_html($ideasCount). '</div>';
                    }

                    if (0 == $postsCount) {
                        $html .= esc_html__('Posts', 'seoaic') . ': ' . esc_html($postsCount);
                    } else {
                        $html .= '
                        <div
                            title="' . esc_html__("Show Posts", "seoaic") . '"
                            class="modal-button mb-5px seoaic-cursor-pointer"
                            data-modal="#keywords-show-created-modal"
                            data-action="seoaic_keyword_get_created_posts"
                            data-id="' . esc_attr($keyword['id']) . '"
                            data-modal-title="' . esc_html__('Created Posts', 'seoaic') . '"
                        >' . esc_html__('Posts', 'seoaic') . ': ' . esc_html($postsCount) . '</div>';
                    }
                $html .= '
                </div>';

                if (
                    empty($keyword['location'])
                    || empty($keyword['lang'])
                ) {
                    $locationHTMLsafe = '<button title="' . esc_html__('Set Country/Location', 'seoaic') . '" type="button"
                        class="seoaic-keyword-add-location-language modal-button"
                        data-modal="#add-keyword-location-language-modal"
                        data-form-callback="keyword_update_location"
                        data-loc-post-id="' . esc_attr($keyword['id']) . '"
                    >
                        <span class="dashicons dashicons-plus"></span> ' . esc_html__('country', 'seoaic') . '
                    </button>';
                } else {
                    $locationHTMLsafe = esc_html($keyword['location']) . '</br>
                    ' . esc_html($keyword['lang']);
                }

                $html .= '
                <div class="location text-center">
                    ' . $locationHTMLsafe . '
                </div>

                <div class="link">'
                . (
                    !empty($keyword['page_link'])
                    ? '<span class="seoaic-keyword-link modal-button dashicons dashicons-admin-links"
                            title="' . esc_attr($linkTitle) . '"
                            data-modal="#add-keyword-link-modal"
                            data-action="seoaic_set_keyword_link"
                            data-form-callback="keyword_update_link_icon"
                            data-link-post-id="' . esc_attr($keyword['id']) . '"
                            data-post-link="' . esc_attr($keyword['page_link']) . '"
                            data-post-link-title="' . esc_attr($keyword['page_link_title']) . '"
                        ></span>'
                    : '<button title="' . esc_html__('Add Link', 'seoaic') . '" type="button"
                            class="seoaic-keyword-add-link modal-button"
                            data-modal="#add-keyword-link-modal"
                            data-action="seoaic_set_keyword_link"
                            data-form-callback="keyword_update_link_icon"
                            data-link-post-id="' . esc_attr($keyword['id']) . '"
                            data-post-link=""
                            data-post-link-title=""
                        >
                            + ' . esc_html__('add link', 'seoaic') . '
                        </button>'
                ) .
                '</div>

                <div class="text-center">';
        if (
            !empty($keyword['child_items'])
            && is_array($keyword['child_items'])
        ) {
            $html .= '<button title="' . esc_html__('Remove with re-assign', 'seoaic') . '" type="button"
                            class="seoaic-remove modal-button confirm-modal-button confirm-remove-and-reassign-modal-button"
                            data-modal="#seoaic-remove-and-reassign-confirm-modal"
                            data-action="seoaic_remove_and_reassign_keyword"
                            data-form-callback="window_reload"
                            data-content="' . esc_html__('This Keyword has child terms. Do you want to remove it?', 'seoaic') . '"
                            data-keyword-id="' . esc_attr($keyword['id']) . '"
                        >
                            <div class="additional-form-items">
                                <input class="seoaic-form-item" type="hidden" name="delete_keyword_id" value="' . esc_attr($keyword['id']) . '">
                            </div>
                    </button>';
        } else {
            $html .= '<button title="' . esc_html__('Remove', 'seoaic') . '" type="button"
                            class="seoaic-remove modal-button confirm-modal-button"
                            data-modal="#seoaic-confirm-modal"
                            data-action="seoaic_remove_keyword"
                            data-form-callback="window_reload"
                            data-content="' . esc_html__('Do you want to remove this keyword?', 'seoaic') . '"
                            data-post-id="' . esc_attr($keyword['id']) . '"
                        ></button>';
        }
        $html .= '</div>
            </div>';

        if (
            !empty($keyword['child_items'])
            && is_array($keyword['child_items'])
        ) {
            $displayClass_safe = $headTermTypeName == $keywordType && !$this->isAnyFilterApplied() ? 'd-none' : '';
            $html .= '<div class="child-items-wrapper position-relative ' . $displayClass_safe . '" id="seoaic_kw_children_' . esc_attr($keyword['id']) . '">';

            foreach ($keyword['child_items'] as $child_item) {
                $html .= $this->makeKeywordRowRecursive($child_item, $keyword['id']);
            }

            $html .= '</div>';
        }

        $html .= '
        </div>';

        return $html;
    }

    private function getTopRank($ranks = [])
    {
        $rankTopPosition = null;

        if (!empty($ranks)) {
            usort($ranks, function ($a, $b) {
                return ($a['position'] < $b['position']) ? -1 : 1;
            });

            $rankTopPosition = !empty($ranks[0]) ? $ranks[0]['position'] : null;
        }

        return $rankTopPosition;
    }

    private static function makeDisplayRankValue($rank, $lastUpdate, array $rankHistory = []): string
    {
        $rankHistory1Year = !empty($rankHistory) ? array_filter($rankHistory, function ($item) {
            return !empty($item['rank'])
                && !empty($item['date'])
                && strtotime("-1 year") < $item['date'];
        }) : [];
        $rankHistory1Year = array_values($rankHistory1Year);

        $rankHistoryAttr = !empty($rankHistory1Year) ? array_map(function ($item) {
            $rank = !empty($item['rank']) ? $item['rank'] : [];
            usort($rank, function ($a, $b) {
                return ($a['position'] < $b['position']) ? -1 : 1;
            });
            $rankTopPosition = !empty($rank[0]) ? $rank[0]['position'] : null;

            return [
                intval($item['date']) * 1000,
                $rankTopPosition,
            ];
        }, $rankHistory1Year) : [];

        $reversedHistory = !empty($rankHistory1Year) ? array_reverse($rankHistory1Year) : [];
        $prevRank = !empty($reversedHistory[0]) ? $reversedHistory[0]['rank'] : [];

        if (!empty($rank)) {
            usort($rank, function ($a, $b) {
                return ($a['position'] < $b['position']) ? -1 : 1;
            });
        }

        if (!empty($prevRank)) {
            usort($prevRank, function ($a, $b) {
                return ($a['position'] < $b['position']) ? -1 : 1;
            });
        }

        $prevRankTopPosition = !empty($prevRank) ? $prevRank[0]['position'] : '-';
        $rankTop = !empty($rank) ? $rank[0] : null;
        $rankTopPosition = !empty($rankTop) ? $rankTop['position'] : '-';
        $rankCount = !empty($rank) ? '(' . count($rank) . ')' : '';

        $rankHistoryAttr[] = [ // add current rank to last position
            intval($lastUpdate) * 1000,
            !empty($rankTop) ? $rankTop['position'] : null
        ];

        $positionChanged = $prevRankTopPosition != $rankTopPosition;
        $newRanked = empty($prevRank) && !empty($rank);

        ob_start();
        ?>
        <div class="seoaic-rank-row d-flex" data-position-changed="<?php echo $positionChanged ? "1" : "";?>" data-new-ranked="<?php echo $newRanked ? "1" : "";?>">
            <div>
                <?php
                if ($positionChanged) {
                    ?>
                    <span class="seoaic-cursor-help" title="<?php esc_html_e('Previous highest rank', 'seoaic');?>"><?php echo esc_html($prevRankTopPosition);?></span><span class="dashicons dashicons-arrow-right-alt seoaic-rank-change-icon"></span>
                    <?php
                }
                ?>
                <span class="seoaic-cursor-help" title="<?php esc_html_e('Current highest rank', 'seoaic');?>"><?php echo esc_html($rankTopPosition);?></span>&nbsp;<span class="seoaic-cursor-help" title="<?php esc_html_e('Total ranked pages', 'seoaic');?>"><?php echo esc_html($rankCount);?></span>
            </div>
            <?php
            if (!empty($rank)) {
                ?>
                <span
                    class="rank-view-all modal-button fs-small"
                    data-modal="#rank-keyword-modal"
                    data-positions="<?php echo esc_attr(json_encode($rank));?>"
                ><?php esc_html_e('view all', 'seoaic');?></span>
                <?php
            }
            ?>
        </div>
        <span
            title="<?php esc_html_e('Rank history', 'seoaic');?>"
            data-modal="#keyword-rank-history"
            data-modal-title="#keyword-rank-history"
            data-title="<?php esc_html_e('Rank History', 'seoaic');?>"
            data-keywords-rank-charts="<?php echo esc_attr(json_encode($rankHistoryAttr));?>"
            class="modal-button rank-view-history fs-small d-flex"
        ><?php esc_html_e('Rank history', 'seoaic');?></span>
        <?php
        return ob_get_clean();
    }

    public static function groupBy($key, $data)
    {
        $result = array();

        foreach ($data as $val) {
            if (array_key_exists($key, $val)) {
                $result[$val[$key]][] = $val;
            } else {
                $result[""][] = $val;
            }
        }

        return $result;
    }

    public function groupByLocationAndLanguage($keywords = []): array
    {
        $keywordsGroupedByLocationAndLang = [];
        $keywordsGroupedByLocation = self::groupBy('location', $keywords);

        foreach ($keywordsGroupedByLocation as $locationName => $_keywords) {
            $keywordsGroupedByLocationAndLang[$locationName] = [];
            $keywordsGroupedByLang = self::groupBy('lang', $_keywords);

            foreach ($keywordsGroupedByLang as $langName => $_keywords2) {
                $keywordsGroupedByLocationAndLang[$locationName][$langName] = $_keywords2;
            }
        }

        return $keywordsGroupedByLocationAndLang;
    }

    public function getSearchVolumes($data, $manual)
    {
        global $SEOAIC_OPTIONS;

        $searchVolumes = [];
        $allKeywordsGrouped = $this->groupByLocationAndLanguage($data['keywords']);

        // $next_update_auto = (30 * DAY_IN_SECONDS) + time();
        // $next_update_manual = (30 * DAY_IN_SECONDS) + time();
        if (!empty($SEOAIC_OPTIONS['keywords_stat_update'])) {
            unset($SEOAIC_OPTIONS['keywords_stat_update']);
        }
        if (!empty($SEOAIC_OPTIONS['keywords_stat_update_manual'])) {
            unset($SEOAIC_OPTIONS['keywords_stat_update_manual']);
        }

        foreach ($allKeywordsGrouped as $location => $languagesData) {
            foreach ($languagesData as $language => $keywordsGroup) {
                $data['keywords'] = array_map(function ($item) {
                    return $item['name'];
                }, $keywordsGroup);
                $data['location'] = !empty($location) ? $location : SEOAIC_SETTINGS::getLocation();
                // $data['language'] = !empty($language) ? $language : SEOAIC_SETTINGS::getLanguage();
                $data['language'] = !empty($language) ? $language : '';

                if (empty($data['language'])) {
                    $firstLang = $this->seoaic->multilang->getFirstLanguageByLocationName($data['location']);
                    $data['language'] = $firstLang['name'];
                }

                if ($kwSearchVolumeResult = $this->requestSearchVolumes($data)) {
                    $searchVolumes = array_merge($searchVolumes, $kwSearchVolumeResult['data']);

                    // $update_auto = isset($kwSearchVolumeResult['KEYWORD_STATS_UPDATE_FREQUENCY']) ? ($kwSearchVolumeResult['KEYWORD_STATS_UPDATE_FREQUENCY'] * HOUR_IN_SECONDS) + time() : $next_update_manual;
                    // $update_manual = isset($kwSearchVolumeResult['KEYWORD_STATS_UPDATE_FREQUENCY_MANUALLY']) ? ($kwSearchVolumeResult['KEYWORD_STATS_UPDATE_FREQUENCY_MANUALLY'] * HOUR_IN_SECONDS) + time() : $next_update_auto;
                    // $update_manual = isset($kwSearchVolumeResult['KEYWORD_STATS_UPDATE_FREQUENCY_MANUALLY']) ? ($kwSearchVolumeResult['KEYWORD_STATS_UPDATE_FREQUENCY_MANUALLY'] * HOUR_IN_SECONDS) + time() : $next_update_auto;

                    // if ($manual) {
                    //     $SEOAIC_OPTIONS['keywords_stat_update_manual'] = $update_auto;
                    //     $SEOAIC_OPTIONS['keywords_stat_update_manual'] = time() + 24 * HOUR_IN_SECONDS; // check only on plugin side
                    // } else {
                    //     $SEOAIC_OPTIONS['keywords_stat_update'] = $update_manual;
                    // }
                }
            }
        }
        update_option('seoaic_options', $SEOAIC_OPTIONS);

        return $searchVolumes;
    }

    public function getSearchIntents($data)
    {
        $searchIntents = [];
        $allKeywordsGrouped = $this->groupByLocationAndLanguage($data['keywords']);

        foreach ($allKeywordsGrouped as $location => $languagesData) {
            foreach ($languagesData as $language => $keywordsGroup) {
                $data['location'] = !empty($location) ? $location : SEOAIC_SETTINGS::getLocation();
                $data['language'] = !empty($language) ? $language : SEOAIC_SETTINGS::getLanguage();

                if ($kwSearchIntentResult = $this->requestSearchIntent($data, $keywordsGroup)) {
                    $searchIntents = array_merge($searchIntents, $kwSearchIntentResult);
                }
            }
        }

        return $searchIntents;
    }

    /**
     * Pulls Search Volumes data for keywords - in chunks by keywords amount
     * @param array $data
     * @return array|false
     */
    public function requestSearchVolumes($data = [])
    {
        $svDataAll = [];
        $keywordsChunks = array_chunk($data['keywords'], self::SEARCH_VOLUME_PER_CHUNK);

        foreach ($keywordsChunks as $key => $keywordsChunk) {
            $data['keywords'] = $keywordsChunk;

            $result = $this->seoaic->curl->init('api/ai/keywords-search-volume', $data, true, true, true);

            if (
                !empty($result['status'])
                && $result['status'] === 'success'
                && isset($result['data'])
                && is_array($result['data'])
            ) {
                $svDataAll = array_merge($svDataAll, $result['data']);
            }
        }

        if (!empty($svDataAll)) {
            $result['data'] = $svDataAll;

            return $result;
        }

        return false;
    }

    /**
     * Pulls Search Intent data for keywords that have no such data
     * @param array $data
     * @param array $keywords
     * @return array|false
     */
    private function requestSearchIntent($data, $keywords = [])
    {
        $filteredKeywords = [];
        $siDataAll = [];
        $keywords = !empty($keywords) && is_array($keywords) ? $keywords : $this->getKeywords();

        // filter only keywords that have no intent label
        foreach ($keywords as $keyword) {
            if (
                !array_key_exists('search_intent_label', $keyword)
                || !$keyword['search_intent_label']
            ) {
                $filteredKeywords[] = $keyword['name'];
            }
        }

        if (!empty($filteredKeywords)) {
            $keywordsChunks = array_chunk($filteredKeywords, self::SEARCH_INTENT_PER_CHUNK);

            foreach ($keywordsChunks as $key => $keywordsChunk) {
                $data['keywords'] = $keywordsChunk;

                $result = $this->seoaic->curl->init('api/ai/keywords-search-intent', $data, true, true, true);

                if (
                    !empty($result['status'])
                    && $result['status'] === 'success'
                    && isset($result['data'])
                    && is_array($result['data'])
                ) {
                    $siDataAll = array_merge($siDataAll, $result['data']);
                }
            }

            if (!empty($siDataAll)) {
                return $siDataAll;
            }
        }

        return false;
    }

    /**
     * Collects Rank data needed for processing in background (AJAX), and stores it as keyword's meta field
     * @param array $data data needed for rank request
     * @param array $keywords array of Keywords
     * @return void
     */
    private function queueRank($data = [], $keywords = [])
    {
        global $SEOAIC_OPTIONS;

        // update rank once per 30 days
        // $filteredKeywords = array_filter($keywords, function ($keyword) {
        //     return empty($keyword['rank_last_update'])
        //         || $keyword['rank_last_update'] + 30 * DAY_IN_SECONDS < time();
        // });
        $filteredKeywords = $keywords;

        if (empty($filteredKeywords)) {
            return false;
        }

        foreach ($filteredKeywords as $keyword) {
            // fix for both: generated keywords and stats update, where each keyword may have different loc/lang
            $language = !empty($data['language']) ? $data['language'] : $keyword['lang'];
            $location = !empty($data['location']) ? $data['location'] : $keyword['location'];

            if (
                !empty($language)
                && !empty($location)
            ) {
                $this->updateKeywordData($keyword, [
                    'rank_request_status' => 'queued',
                    'rank_request_data' => [
                        'email'     => $data['email'],
                        'token'     => $SEOAIC_OPTIONS['seoaic_api_token'],
                        'language'  => $language,
                        'location'  => $location,
                        'target'    => SEOAIC_SETTINGS::getCompanyWebsite('host'),
                        'add_terms' => $keyword['name'],
                    ],
                ]);
            }
        }
    }

    /**
     * Gets Keywords with rank data to be requested, 5 per time. Note: Keywords are in WP Post format.
     * @return array array of post objects
     */
    private function getKeywordsWithQueuedRank()
    {
        $keywords = get_posts([
            'post_type' => self::KEYWORD_POST_TYPE,
            'post_status' => 'any',
            // 'numberposts' => -1,
            'numberposts' => 5,
            'meta_query' => [
                [
                    'key'   => 'rank_request_status',
                    'value' => 'queued',
                ]
            ],
            'lang' => '',
        ]);

        return $keywords;
    }

    /**
     * Sends request to backend and returns response
     * @param array $rankRequestData array of fields neede for request
     * @return array|false
     */
    private function requestRank($rankRequestData = [])
    {
        $rankSearchTermsResult = $this->seoaic->curl->init('api/ai/rank-search-terms', $rankRequestData, true, true, true);

        if (
            !empty($rankSearchTermsResult['status'])
            && $rankSearchTermsResult['status'] === 'success'
        ) {
            return $rankSearchTermsResult;
        }

        return false;
    }

    /**
     * Gets Rank data, updates Keyword's Rank meta field
     * @return array|false
     */
    private function backgroundProcessRank()
    {
        $keywordsToProcess = $this->getKeywordsWithQueuedRank();
        $result = [];
        $time = time();

        if (!empty($keywordsToProcess)) {
            try {
                foreach ($keywordsToProcess as $k) {
                    $this->updateKeywordData($k->ID, [
                        'rank_request_status' => 'requested',
                    ]);
                }

                foreach ($keywordsToProcess as $keywordToProcess) {
                    $rankDataNew = [];
                    $rankDataCurrent = get_post_meta($keywordToProcess->ID, 'rank_data', true);
                    delete_post_meta($keywordToProcess->ID, 'prev_rank_data');
                    $rankHistory = get_post_meta($keywordToProcess->ID, 'rank_history', true);
                    $rankRequestData = get_post_meta($keywordToProcess->ID, 'rank_request_data', true);
                    $rankLastUpdate = get_post_meta($keywordToProcess->ID, 'rank_last_update', true);
                    $keywordNewData = [
                        'rank_request_status'   => 'completed',
                        'rank_last_update'      => $time,
                    ];

                    $keywordNewData['rank_history'] = empty($rankHistory) ? [] : $rankHistory;
                    $keywordNewData['rank_history'][] = [ // add current to the last position
                        'rank' => $rankDataCurrent,
                        'date' => $rankLastUpdate,
                    ];

                    $rankSearchTermsResult = $this->requestRank($rankRequestData);

                    if (
                        !empty($rankSearchTermsResult['data'])
                        && is_array($rankSearchTermsResult['data'])
                    ) {
                        foreach ($rankSearchTermsResult['data'] as $row) {
                            $rankDataNew[] = [
                                'position'  => !empty($row['position']) ? $row['position'] : '',
                                'page'      => !empty($row['page']) ? $row['page'] : '',
                            ];
                        }
                    }

                    $keywordNewData['rank_data'] = $rankDataNew;

                    $this->updateKeywordData($keywordToProcess->ID, $keywordNewData);

                    $rankHistory = !empty($keywordNewData['rank_history']) ? $keywordNewData['rank_history'] : [];
                    $result[] = [
                        'id' => $keywordToProcess->ID,
                        'rank_data' => $rankDataNew,
                        'html' => self::makeDisplayRankValue($rankDataNew, $time, $rankHistory),
                    ];
                }
            } catch (Exception $e) {
                foreach ($keywordsToProcess as $k) {
                    $this->updateKeywordData($k->ID, [
                        'rank_request_status' => 'queued',
                    ]);
                }
            }

            return $result;
        }

        return false;
    }

    /**
     * Checks if there are any Keywords with Rank field in 'queued' status
     */
    public function isBackgroundRankProcessInProgress()
    {
        $keywords = $this->getKeywordsWithQueuedRank();

        if (!empty($keywords)) {
            return true;
        }

        return false;
    }

    public function getRankBulkAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if ($result = $this->backgroundProcessRank()) {
            SEOAICAjaxResponse::success()->addFields([
                'completed' => false,
                'data' => $result,
            ])->wpSend();
        }

        SEOAICAjaxResponse::success()->addFields([
            'completed' => true,
            'data' => [],
        ])->wpSend();
    }

    private function requestKeywordSerp($keyword)
    {
        $data = [
            'location' => !empty($keyword['location']) ? $keyword['location'] : SEOAIC_SETTINGS::getLocation(),
            'language' => !empty($keyword['lang']) ? $keyword['lang'] : SEOAIC_SETTINGS::getLanguage(),
            'email'    => wp_get_current_user()->user_email,
            'keywords' => [$keyword['name']],
        ];

        $result = $this->seoaic->curl->init('api/ai/keywords-serp-competitors', $data, true, false, true);

        if (
            !empty($result['status'])
            && 'success' === $result['status']
            && !empty($result['data'])
            && !empty($result['data'][$keyword['name']])
        ) {
            return $result['data'][$keyword['name']];
        }

        return [];
    }

    // private function updateKeywordSerp($keyword)
    // {
    //     $keywordSerp = $this->requestKeywordSerp($keyword);

    //     return $this->updateKeywordData($keyword, [
    //         'serp_last_update'  => time(),
    //         'serp_data'         => $keywordSerp,
    //     ]);
    // }

    // public function getKeywordSerp()
    // {
    //     $keyword = null;

    //     if (
    //         !empty($_POST['id'])
    //         && is_numeric($_POST['id'])
    //     ) {
    //         $keyword = $this->getKeywordByID($_POST['id']);
    //     }

    //     if (
    //         is_null($keyword)
    //         || $keyword['slug'] != $_POST['keyword']
    //     ) {
    //         SEOAICAjaxResponse::error('No Keyword found!')->wpSend();
    //     }

    //     if ($this->isSERPDataValid($keyword)) {
    //         SEOAICAjaxResponse::success()->addFields([
    //             'serp' => maybe_unserialize($keyword['serp_data']),
    //         ])->wpSend();

    //     } else {
    //         if ($this->updateKeywordSerp($keyword)) {
    //             $keyword = $this->getKeywordByID($keyword['id']);

    //             SEOAICAjaxResponse::success()->addFields([
    //                 'serp' => maybe_unserialize($keyword['serp_data']),
    //             ])->wpSend();
    //         }
    //     }

    //     SEOAICAjaxResponse::error('No Keyword data found!')->wpSend();
    // }

    private function getSERPLastUpdate($keyword)
    {
        $serpDate = esc_html__('never', 'seoaic');

        if (array_key_exists('serp_data', $keyword)) {
            $serp_time = $keyword['serp_last_update'];

            if (time() - $serp_time < DAY_IN_SECONDS * 7) {
                $serpDate = date('M j, Y', $serp_time);
            }
        }

        return $serpDate;
    }

    private function isSERPDataValid($keyword)
    {
        return array_key_exists('serp_data', $keyword)
            && time() - $keyword['serp_last_update'] < DAY_IN_SECONDS * 7;
    }

    public function setKeywordLinkAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_REQUEST['post_id'])
            || !is_numeric($_REQUEST['post_id'])
        ) {
            SEOAICAjaxResponse::error('Wrong ID parameter.')->wpSend();
        }

        $link = !empty($_REQUEST['page_link']) ? trim($_REQUEST['page_link']) : '';

        if ($result = $this->setKeywordLink(intval($_REQUEST['post_id']), $link)) {
            $keyword = $this->getKeywordByID($_REQUEST['post_id']);
            $linkTitle = !empty($keyword['page_link_title']) ? $keyword['page_link_title'] : (!is_numeric($keyword['page_link']) ? $keyword['page_link'] : '');
            $content = '<span class="seoaic-keyword-link modal-button dashicons dashicons-admin-links"
                title="' . esc_attr($linkTitle) . '"
                data-modal="#add-keyword-link-modal"
                data-action="seoaic_set_keyword_link"
                data-form-callback="keyword_update_link_icon"
                data-link-post-id="' . esc_attr($_REQUEST['post_id']) . '"
                data-post-link="' . esc_attr($link) . '"
                data-post-link-title="' . esc_attr($linkTitle) . '"></span>';

            if (empty($link)) {
                $content = '<button title="' . esc_html__('Add Link', 'seoaic') . '" type="button"
                    class="seoaic-keyword-add-link modal-button"
                    data-modal="#add-keyword-link-modal"
                    data-action="seoaic_set_keyword_link"
                    data-form-callback="keyword_update_link_icon"
                    data-post-id="' . esc_attr($_REQUEST['post_id']) . '"
                    data-post-link=""
                    data-post-link-title=""
                >
                    <span class="dashicons dashicons-plus"></span> ' . esc_html__('add link', 'seoaic') . '
                </button>';
            }

            SEOAICAjaxResponse::alert('Link updated')->addFields([
                'content' => [
                    'id' => $_REQUEST['post_id'],
                    'content' => $content,
                ],
            ])->wpSend();
        }

        SEOAICAjaxResponse::error('Unknown error.')->wpSend();
    }

    private function categoryAdd($categoryName = '')
    {
        $result = wp_insert_category([
            'taxonomy' => self::KEYWORDS_CATEGORIES_KEY,
            'cat_name' => trim($categoryName),
            'category_description' => '',
            'category_nicename' => sanitize_title(trim($categoryName)),
            'category_parent' => '',
        ], true);

        return $result;
    }

    private function categoryAddIfNotExists($categoryName = '')
    {
        $categoryId = category_exists($categoryName);

        if ($categoryId) {
            return (int)$categoryId;
        }

        $result = wp_insert_term(
            trim($categoryName),
            self::KEYWORDS_CATEGORIES_KEY,
            [
                'slug' => sanitize_title($categoryName),
                'description' => '',
                'parent' => 0,
            ]
        );

        if (is_wp_error($result)) {
            if ($result->get_error_code() === 'term_exists') {
                return (int)$result->get_error_data('term_exists');
            }
            return 0;
        }

        return (int)$result['term_id'];
    }

    public function categoryAddAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (empty(trim($_REQUEST['category_name']))) {
            SEOAICAjaxResponse::error('Empty category name')->wpSend();
        }

        $result = $this->categoryAdd($_REQUEST['category_name']);

        if (is_wp_error($result)) {
            SEOAICAjaxResponse::error('Error:' . $result->get_error_message())->wpSend();
        }

        $category = get_term_by('term_id', $result, self::KEYWORDS_CATEGORIES_KEY);

        SEOAICAjaxResponse::success()->addFields([
            'html' => self::makeKeywordCategoryRow($category),
        ])->wpSend();
    }

    /**
     * Sets/unsets category (cluster) for provided keywords IDs, separated by comma
     */
    public function categorySetAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_REQUEST['keyword_id'])
            || !isset($_REQUEST['category_id'])
        ) {
            SEOAICAjaxResponse::alert('Not enough parameters!')->wpSend();
        }

        $keywordIDs = [];
        $categoryID = $this->maybeAddCategory($_REQUEST['category_id']);

        if (is_numeric($_REQUEST['keyword_id'])) {
            $keywordIDs[] = intval($_REQUEST['keyword_id']);

        } elseif (false !== strpos($_REQUEST['keyword_id'], ',')) {
            $keywordIDs = explode(',', $_REQUEST['keyword_id']);
            $keywordIDs = array_map('trim', $keywordIDs);
            $keywordIDs = array_filter($keywordIDs);
            $keywordIDs = array_unique($keywordIDs);
            $keywordIDs = array_map('intval', $keywordIDs);
        }

        if (
            empty($keywordIDs)
            || !is_array($keywordIDs)
        ) {
            SEOAICAjaxResponse::alert('Wrong parameters!')->wpSend();
        }

        foreach ($keywordIDs as $keywordID) {
            $result = $this->setKeywordCategory($keywordID, $categoryID);

            if (is_wp_error($result)) {
                SEOAICAjaxResponse::error('Error: ' . $result->get_error_message())->wpSend();
            }
        }

        SEOAICAjaxResponse::success()->wpSend();
    }

    private function setKeywordCategory(int $keywordID, $categoryID)
    {
        if (empty($categoryID)) { // unset all
            $result = wp_set_object_terms($keywordID, [], self::KEYWORDS_CATEGORIES_KEY);

        } else {
            $result = wp_set_object_terms($keywordID, [intval($categoryID)], self::KEYWORDS_CATEGORIES_KEY);
        }

        return $result;
    }


    public function getKeywordsCategories()
    {
        return get_categories([
            'taxonomy'      => self::KEYWORDS_CATEGORIES_KEY,
            'hide_empty'    => false,
            'orderby'       => 'name',
            'order'         => 'ASC',
        ]);
    }

    public function makeKeywordsCategoriesOptions($keywordsCategories, $addNonCategories = true)
    {
        $html = '';
        $options = [];

        if ($addNonCategories) {
            $options[''] = esc_html__('All Clusters', 'seoaic');
            $options['_without_cluster'] = esc_html__('Without Cluster', 'seoaic');
        }

        if (!empty($keywordsCategories)) {
            foreach ($keywordsCategories as $category) {
                $options[$category->term_id] = $category->name;
            }
        }

        foreach ($options as $value => $name) {
            $selected = '';
            if (
                !empty($_GET['cluster'])
                && $_GET['cluster'] == $value
            ) {
                $selected = 'selected';
            }
            $html .= '<option ' . $selected . ' value="' . esc_attr($value) . '">' . esc_html($name) . '</option>';
        }

        return $html;
    }
    public function makeKeywordsIntentsOptions()
    {
        $html = '';
        $options = [
            ''              => esc_html__('All Search Intent', 'seoaic'),
            'informational' => esc_html__('Informational', 'seoaic'),
            'navigational'  => esc_html__('Navigational', 'seoaic'),
            'commercial'    => esc_html__('Commercial', 'seoaic'),
            'transactional' => esc_html__('Transactional', 'seoaic'),
        ];

        foreach ($options as $value => $name) {
            $selected = '';
            if (
                !empty($_GET['intent'])
                && $_GET['intent'] == $value
            ) {
                $selected = 'selected';
            }
            $html .= '<option ' . $selected . ' value="' . esc_attr($value) . '">' . esc_html($name) . '</option>';
        }

        return $html;
    }

    public function getKeywordsCategoriesAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $keywordsCategories = $this->getKeywordsCategories();
        $fields = [
            'categories' => $keywordsCategories,
        ];

        if (
            !empty($_REQUEST['options_html'])
            && 1 == $_REQUEST['options_html']
        ) {
            $fields['options_html'] = '<option value="">None</option>';
            $fields['options_html'] .= $this->makeKeywordsCategoriesOptions($keywordsCategories, false);
        }

        SEOAICAjaxResponse::success()->addFields($fields)->wpSend();
    }

    public function deleteKeywordsCategory()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_REQUEST['category_id'])
            || !is_numeric($_REQUEST['category_id'])
        ) {
            SEOAICAjaxResponse::error('Wrong request parameters!')->wpSend();
        }

        $result = wp_delete_term($_REQUEST['category_id'], self::KEYWORDS_CATEGORIES_KEY);

        if (is_wp_error($result)) {
            SEOAICAjaxResponse::error('Error: ' . $result->get_error_message())->wpSend();
        } elseif (false == $result) {
            SEOAICAjaxResponse::error('Category was not removed.')->wpSend();
        }

        SEOAICAjaxResponse::success()->wpSend();
    }

    public function updateKeywordsCategory()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_REQUEST['category_id'])
            || !is_numeric($_REQUEST['category_id'])
        ) {
            SEOAICAjaxResponse::error('Wrong request parameters!')->wpSend();
        }

        $categoryName = sanitize_text_field(trim($_REQUEST['category_name']));
        $result = wp_update_term($_REQUEST['category_id'], self::KEYWORDS_CATEGORIES_KEY, [
            'name' => $categoryName,
            'slug' => sanitize_title($categoryName),
        ]);

        if (is_wp_error($result)) {
            SEOAICAjaxResponse::error('Error: ' . $result->get_error_message())->wpSend();
        } elseif (false == $result) {
            SEOAICAjaxResponse::error('Category was not updated.')->wpSend();
        }

        SEOAICAjaxResponse::success()->addFields([
            'category_name' => $categoryName,
        ])->wpSend();
    }

    public static function makeKeywordCategoryRow($category)
    {
        ob_start();
        ?>
        <div class="table-row">
            <div class="titles-col">
                <span><?php echo esc_html($category->name);?></span>
                <input type="text" name="category_name" value="<?php echo esc_attr($category->name);?>" class="dn">
            </div>
            <div class="buttons-col">
                <div class="edit-remove-buttons">
                    <button title="<?php esc_attr_e('Update', 'seoaic');?>" type="button" class="seoaic-edit seoaic-edit-category-button"></button>
                    <button title="<?php esc_attr_e('Remove', 'seoaic');?>" type="button" class="seoaic-remove seoaic-remove-category-button"></button>
                </div>

                <div class="update-confirm-buttons dn">
                    <?php esc_html_e('Update', 'seoaic');?>?
                    <button type="button" class="seoaic-cancel-btn  seoaic-update-category-button-cancel"><?php esc_html_e('Cancel', 'seoaic');?></button>
                    <button type="button" class="seoaic-confirm-btn seoaic-update-category-button-confirm" data-cat-id="<?php echo esc_attr($category->term_id);?>"><?php esc_html_e('Confirm', 'seoaic');?></button>
                </div>

                <div class="remove-confirm-buttons dn">
                    <?php esc_html_e('Remove', 'seoaic');?>?
                    <button type="button" class="seoaic-cancel-btn seoaic-remove-category-button-cancel"><?php esc_html_e('Cancel', 'seoaic');?></button>
                    <button type="button" class="seoaic-confirm-btn seoaic-remove-category-button-confirm" data-cat-id="<?php echo esc_attr($category->term_id);?>"><?php esc_html_e('Confirm', 'seoaic');?></button>
                </div>
            </div>
        </div>
        <?php
        return ob_get_clean();
    }

    public function getCreatedIdeasAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_POST['id'])
            || !is_numeric($_POST['id'])
        ) {
            SEOAICAjaxResponse::error('No ID set.')->wpSend();
        }

        $keyword = $this->getKeywordByID($_POST['id']);

        if (empty($keyword['id'])) {
            SEOAICAjaxResponse::error('No keyword found.')->wpSend();
        }

        $html = '';
        $ideas = !empty($keyword['ideas_created']) ? explode(',', $keyword['ideas_created']) : [];

        if (!empty($ideas)) {
            foreach ($ideas as $id) {
                $idea = get_post($id);
                if (
                    $idea
                    && SEOAIC_IDEAS::isIdea($idea)
                ) {
                    $html .= self::makeKeywordCreatedIdeaRow($idea);
                }
            }
        }

        SEOAICAjaxResponse::success()->addFields([
            'html' => $html
        ])->wpSend();
    }

    public function getCreatedPostsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_POST['id'])
            || !is_numeric($_POST['id'])
        ) {
            SEOAICAjaxResponse::error('No ID set.')->wpSend();
        }

        $keyword = $this->getKeywordByID($_POST['id']);

        if (empty($keyword['id'])) {
            SEOAICAjaxResponse::error('No keyword found.')->wpSend();
        }

        $html = '';
        $ideas = !empty($keyword['posts_created']) ? explode(',', $keyword['posts_created']) : [];

        if (!empty($ideas)) {
            foreach ($ideas as $id) {
                $idea = get_post($id);
                if (
                    $idea
                    && !SEOAIC_IDEAS::isIdea($idea)
                ) {
                    $html .= self::makeKeywordCreatedPostRow($idea);
                }
            }
        }

        SEOAICAjaxResponse::success()->addFields([
            'html' => $html
        ])->wpSend();
    }

    public static function makeKeywordCreatedIdeaRow($idea)
    {
        ob_start();
        if (!empty($idea)) {
            ?>
            <div class="table-row">
                <div class="text-start">
                    <b class="mr-15">#<?php echo esc_html($idea->ID);?></b><span><?php echo esc_html($idea->post_title);?></span>
                </div>
            </div>
            <?php
        }

        return ob_get_clean();
    }

    public static function makeKeywordCreatedPostRow($post)
    {
        ob_start();
        if (!empty($post)) {
            ?>
            <div class="table-row">
                <div class="text-start">
                    <b class="mr-15"><a href="<?php echo esc_attr(get_edit_post_link($post->ID)); ?>" target="_blank">#<?php echo esc_html($post->ID);?> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></svg></a></b><span><?php echo esc_html($post->post_title);?></span>
                </div>
            </div>
            <?php
        }

        return ob_get_clean();
    }

    public function trackKeyword()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_POST['id'])
            || !is_numeric($_POST['id'])
        ) {
            SEOAICAjaxResponse::error('No ID set.')->wpSend();
        }

        $keyword = $this->getKeywordByID($_POST['id']);

        if (empty($keyword['id'])) {
            SEOAICAjaxResponse::error('No keyword found.')->wpSend();
        }

        $data = [
            'is_tracked' => (isset($_POST['tracked']) ? $_POST['tracked'] : false),
        ];

        if ($this->updateKeywordData($keyword['id'], $data)) {
            SEOAICAjaxResponse::success()->wpSend();
        }

        SEOAICAjaxResponse::error('Error')->wpSend();
    }

    public function getCompetitorKeywordsTabsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        SEOAICAjaxResponse::success()->addFields([
            'tabs' => $this->seoaic->competitors->keywordsRankedLocationsTabsHTML(),
        ])->wpSend();
    }

    public function getCompetitorKeywords()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $location = $this->seoaic->competitors->get_the_location_param();

        if (!empty($_REQUEST['location'])) {
            $location = $_REQUEST['location'];
        }

        $first_index = $this->seoaic->competitors->our_own_competitor_id($location);
        $_REQUEST['index'] = $first_index;

        $this->seoaic->competitors->Competitors_Search_Terms_HTML();
    }

    public function setKeywordLocation()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        if (
            empty($_POST['post_id'])
            || !is_numeric($_POST['post_id'])
        ) {
            SEOAICAjaxResponse::error('No ID set.')->wpSend();
        }

        if (
            empty($_REQUEST['location'])
            || empty($_REQUEST['language'])
        ) {
            SEOAICAjaxResponse::error('Location/Language can`t be empty!')->wpSend();
        }

        $keyword = $this->getKeywordByID($_POST['post_id']);

        if (empty($keyword['id'])) {
            SEOAICAjaxResponse::error('No keyword found.')->wpSend();
        }

        $data = [
            'location' => sanitize_text_field($_REQUEST['location']),
            'language' => sanitize_text_field($_REQUEST['language']),
        ];

        if ($this->updateKeywordData($keyword['id'], $data)) {
            $content = esc_html($data['location']) . '</br>' . esc_html($data['language']);

            SEOAICAjaxResponse::success()->addFields([
                'content' => [
                    'id' => $_REQUEST['post_id'],
                    'content' => $content,
                ],
            ])->wpSend();
        }

        SEOAICAjaxResponse::error('Error')->wpSend();
    }

    // MIGRATION SEARCH TERMS TO KEYWORDS START
    /**
     * @param $name
     * @param $location
     * @return mixed|null
     */
    public function getKeywordIDByNameLocation($name = '', $location = '')
    {
        global $wpdb;

        $query = "SELECT
        p.ID as id
        FROM {$wpdb->prefix}posts p
        LEFT JOIN {$wpdb->prefix}postmeta location__meta ON p.ID = location__meta.post_id AND location__meta.meta_key = 'location'
        WHERE p.post_type = %s";

        if (!empty($name)) {
            $query .= $wpdb->prepare(" AND p.post_title = %s", $name);
        }

        if (!empty($location)) {
            $query .= $wpdb->prepare(" AND location__meta.meta_value = %s", $location);
        }

        $query .= " LIMIT 1";

        $preparedQuery = $wpdb->prepare($query, [self::KEYWORD_POST_TYPE]);

        $keyword = $wpdb->get_row($preparedQuery, ARRAY_A);

        return $keyword ? $keyword['id'] : null;
    }

    /**
     * @return false|string
     */
    public function isNotMigratedTerms()
    {

        global $SEOAIC_OPTIONS;

        $terms = isset($SEOAIC_OPTIONS['search_terms']['google']) && !empty($SEOAIC_OPTIONS['search_terms']['google']) ? $SEOAIC_OPTIONS['search_terms']['google'] : [];

        $notMigrated = array_filter($terms, function ($item) {
            return !array_key_exists('in_keywords', $item);
        });

        if (!empty($notMigrated)) {
            return ' data-not-migrated-terms-exists="true"';
        }

        return false;
    }

    /**
     * @param $slug
     * @return false|mixed
     */
    public function getSearchTermBySlug($slug)
    {
        global $SEOAIC_OPTIONS;

        $terms = $SEOAIC_OPTIONS['search_terms']['google'];
        $keyword = false;

        foreach ($terms as $item) {
            if (
                trim($item['slug'])
                ===
                trim($slug)) {
                $keyword = $item['keyword'];
                break;
            }
        }

        return $keyword;
    }

    /**
     * @param $keywordsArray
     * @param $location
     * @param $parentPostId
     * @return array[]|false
     */
    public function migrateTermsToKeywords($keywordsArray, $location, $parentPostId)
    {

        global $SEOAIC_OPTIONS;

        $language = $this->seoaic->multilang->getFirstLanguageByLocationName($location)['name'];

        $keywordsArray = array_unique($keywordsArray);
        if (empty($keywordsArray)) {
            return false;
        }

        $skippedKeywords = [];
        $skippedKeywords[] = $this->removeDuplicatedKeywordsFromInput($keywordsArray);
        if(!empty($skippedKeywords)) {
            $this->setSearchTermsAsMigrated([$location => $skippedKeywords], false);
        }

        $ids = [];
        $addedKeywordsNames = [];
        $keywordType = '';

        if ($parentPostId) {
            $keywordType = 'mid_tail_term';
            $this->isMidTailTermType($keywordType);
        }

        foreach ($keywordsArray as $keywordName) {
            if ($id = $this->addKeyword($keywordName, $parentPostId)) {
                $ids[] = $id;
                $addedKeywordsNames[] = strtolower($keywordName);
                $this->setKeywordType($id, $keywordType);

                if(
                    $this->isHeadTermType($keywordType)
                    && !empty($_REQUEST['seoaic_keywords_categories'])
                ) {
                    $this->setKeywordCategory($id, $_REQUEST['seoaic_keywords_categories']);
                }

                $data = [
                    'is_tracked' => 'true',
                ];

                $this->updateKeywordData($id, $data);

            }
        }

        $addedKeywords = $this->getKeywordsByIDs($ids);

        if (!$addedKeywords) {
            return false;
        }

        $fieldsData = [
            'keywords' => $addedKeywords,
            'location' => $location,
            'language' => $language,
            'search_volumes' => [],
            'search_intents' => [],
        ];

        $data = [
            'keywords' => $keywordsArray,
            'location' => $location,
            'language' => $language,
            'mode' => 'auto',
            'email' => $SEOAIC_OPTIONS['seoaic_api_email'],
        ];

        if ($kwSearchVolumeResult = $this->requestSearchVolumes($data)) {
            $fieldsData['search_volumes'] = $kwSearchVolumeResult['data'];
        }

        if ($keywordsSearchIntents = $this->requestSearchIntent($data, $addedKeywords)) {
            $fieldsData['search_intents'] = $keywordsSearchIntents;
        }

        $this->queueRank($data, $addedKeywords);
        $this->updateKeywordsFields($fieldsData);

        return [$location => $addedKeywordsNames];
    }

    /**
     * @param $migratedArray
     * @param $migrated_status
     * @return array
     */
    public function setSearchTermsAsMigrated($migratedArray, $migrated_status)
    {

        global $SEOAIC_OPTIONS;

        $searchTerms = $SEOAIC_OPTIONS['search_terms']['google'];

        $migrated = [];

        foreach ($migratedArray as $item) {
            if ($item && is_array($item)) {
                foreach ($item as $country => $terms) {
                    if (!isset($migrated[$country])) {
                        $migrated[$country] = [];
                    }
                    $migrated[$country] = array_merge($migrated[$country], $terms);
                }
            }
        }

        foreach ($migrated as $country => $keywords) {
            foreach ($keywords as $keyword) {
                foreach ($searchTerms as $index => $item) {
                    if ($item['keyword'] === $keyword) {
                        $SEOAIC_OPTIONS['search_terms']['google'][$index]['in_keywords'] = $migrated_status;
                    }
                }
            }
        }

        update_option('seoaic_options', $SEOAIC_OPTIONS);

        return $migrated;
    }

    /**
     * @return void
     */
    public function migrateTermsToKeywordsAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        global $SEOAIC_OPTIONS;

        $terms = isset($SEOAIC_OPTIONS['search_terms']['google']) && !empty($SEOAIC_OPTIONS['search_terms']['google']) ? $SEOAIC_OPTIONS['search_terms']['google'] : [];
        $terms = array_filter($terms, function($item) {
            return !isset($item['in_keywords']);
        });

        $locationsArray = [];
        $getParentSearchTerms = [];
        $addedItems = [];
        foreach ($terms as $item) {
            $location = $item['location'];
            $parentTerm = $item['parent_term'] ?: 0;

            $locationsArray[$location][$parentTerm][] = $item['keyword'];
            if ($parentTerm) {
                $getParentSearchTerms[$location][] = $this->getSearchTermBySlug($parentTerm);
            }
        }

        //  migrate parent has-children keywords first
        foreach ($getParentSearchTerms as $location => $parent) {
            $parent = array_unique($parent);
            $result = $this->migrateTermsToKeywords($parent, $location, 0);
            $addedItems[] = $result;
        }

        //  migrate children keywords and others
        foreach ($locationsArray as $location => $parentSearchTermsArray) {
            foreach ($parentSearchTermsArray as $parent => $searchTermsArray) {
                $parentID = 0;
                if ($parent) {
                    $parentID = $this->getKeywordIDByNameLocation($this->getSearchTermBySlug($parent), $location);
                }
                $result = $this->migrateTermsToKeywords($searchTermsArray, $location, $parentID);
                $addedItems[] = $result;
            }
        }

        $this->setSearchTermsAsMigrated($addedItems, true);

        if(!empty($addedItems)) {
            $message[] = 'Search terms migrated to keywords successful!';
            WPTransients::deleteCachedValue(self::KEYWORDS_CACHE_KEY);
            SEOAICAjaxResponse::alert(implode('<br/>', $message))->wpSend();
        }

        wp_die();
    }
    // MIGRATION SEARCH TERMS TO KEYWORDS END

    public function getKeywordInternalLinksAllAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $__REQUEST = wp_unslash($_REQUEST);
        $internalLinks = [];
        $IDs = [];

        if (!empty($__REQUEST['ids'])) {
            if (is_numeric($__REQUEST['ids'])) {
                $IDs = [$__REQUEST['ids']];

            } else if (is_array($__REQUEST['ids'])) {
                $IDs = $__REQUEST['ids'];
            }

            $internalLinks = $this->seoaic->internal_links->forKeywords($IDs, null, 'post')->toOptionsDataArray();
        }

        SEOAICAjaxResponse::success()->addFields([
            'internalLinks' => $internalLinks,
        ])->wpSend();
    }

    public function getKeywordInternalLinksRecommendedAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $__REQUEST = wp_unslash($_REQUEST);
        $internalLinks = [];
        $IDs = [];

        if (!empty($__REQUEST['ids'])) {
            if (is_numeric($__REQUEST['ids'])) {
                $IDs = [$__REQUEST['ids']];

            } else if (is_array($__REQUEST['ids'])) {
                $IDs = $__REQUEST['ids'];
            }

            $internalLinks = $this->seoaic->internal_links->forKeywords($IDs, 5)->toOptionsDataArray();
        }

        SEOAICAjaxResponse::success()->addFields([
            'internalLinks' => $internalLinks,
        ])->wpSend();
    }

    public function getInternalLinksRandomAjax()
    {
        check_ajax_referer(SeoaicAjaxValidation::ACTION_STRING);

        $internalLinks = [];
        $n = 100;

        if ($this->seoaic->multilang->is_multilang()) {
            $multilangCurrentLang = $this->seoaic->multilang->get_current_language('code');
            $selectedLanguages = $this->seoaic->multilang->filter_request_multilang();
            $selectedLanguageNames =
                is_array($selectedLanguages) && !empty($selectedLanguages['name'])
                ? [$selectedLanguages['name']]
                : (
                    is_string($selectedLanguages)
                    ? explode(',', $selectedLanguages)
                    : []
                );

            if (!empty($selectedLanguageNames)) {
                foreach ($selectedLanguageNames as $selectedLanguageName) {
                    $language = $this->seoaic->multilang->get_language_by($selectedLanguageName, 'name');
                    $internalLinksForLang = $this->seoaic->internal_links->random($n, [], $language['code'])->toOptionsDataArray();
                    // important for WPML. Don't forget to set the language back to the original one.
                    $this->seoaic->multilang->postProcessPostsMainQuery($multilangCurrentLang);

                    $internalLinks = array_merge($internalLinks, $internalLinksForLang);
                }
            }

        } else {
            $internalLinks = $this->seoaic->internal_links->random($n)->toOptionsDataArray();
        }

        SEOAICAjaxResponse::success()->addFields([
            'internalLinks' => $internalLinks,
        ])->wpSend();
    }
}
