<?php

if (! defined('ABSPATH')) {
    exit;
} class PufferGo_Related_Products
{
    public const TAXONOMY = 'puffergo_product_cat';

    public const CANDIDATE_CAP = 100;

    private $source_id;

    private $source_title;

    private $category_ids;

    public function __construct($source_id, array $category_ids)
    {
        $this->source_id = (int) $source_id;
        $this->source_title = get_the_title($this->source_id);
        $this->category_ids = array_values(array_unique(array_map('intval', $category_ids)));
    }

    public function get_ids($limit)
    {
        $limit = (int) $limit;
        $picked = array();
        $exclude = array($this->source_id);

        foreach ($this->tiers() as $tier) {
            $need = $limit - count($picked);

            if ($need <= 0) {
                break;
            }

foreach ($this->query_tier($tier, $exclude, $need) as $id) {
                $picked[] = $id;
                $exclude[] = $id;

                if (count($picked) >= $limit) {
                    break;
                }
            }
        }

return $picked;
    }

    private function tiers()
    {
        $tiers = array();

        if ($this->category_ids) {
            $tiers[] = array('scored' => true, 'args' => array('tax_query' => array(array('taxonomy' => self::TAXONOMY, 'field' => 'term_id', 'terms' => $this->category_ids, ))), );
            $parent_ids = $this->parent_category_ids();

            if ($parent_ids) {
                $tiers[] = array('scored' => true, 'args' => array('tax_query' => array(array('taxonomy' => self::TAXONOMY, 'field' => 'term_id', 'terms' => $parent_ids, 'include_children' => true, ))), );
            }
        } $tiers[] = array('scored' => false, 'args' => array());

        return $tiers;
    }

    private function parent_category_ids()
    {
        $parents = array();

        foreach ($this->category_ids as $cid) {
            $parent = (int) get_term_field('parent', $cid, self::TAXONOMY);

            if ($parent) {
                $parents[] = $parent;
            }
        }

return array_values(array_unique($parents));
    }

    private function query_tier(array $tier, array $exclude, $need)
    {
        $ids = array_map('intval', get_posts(array_merge(array('post_type' => 'puffergo_product', 'post_status' => 'publish', 'posts_per_page' => $tier['scored'] ? self::CANDIDATE_CAP : $need, 'post__not_in' => $exclude, 'fields' => 'ids', 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, 'ignore_sticky_posts' => true, ), $tier['args'])));

        if (! $tier['scored'] || count($ids) < 2) {
            return $ids;
        } _prime_post_caches($ids, false, false);
        $scores = array();

        foreach ($ids as $id) {
            $scores[ $id ] = $this->similarity($this->source_title, get_the_title($id));
        } usort($ids, function ($a, $b) use ($scores) { return $scores[ $b ] <=> $scores[ $a ]; });

        return $ids;
    }

    private function similarity($a, $b)
    {
        $grams_a = $this->bigrams($a);
        $grams_b = $this->bigrams($b);

        if (empty($grams_a) || empty($grams_b)) {
            return 0.0;
        } $b_count = array_count_values($grams_b);
        $overlap = 0;

        foreach ($grams_a as $gram) {
            if (! empty($b_count[ $gram ])) {
                $overlap++;
                $b_count[ $gram ]--;
            }
        }

return (2 * $overlap) / (count($grams_a) + count($grams_b));
    }

    private function bigrams($text)
    {
        $text = preg_replace('/\s+/u', '', mb_strtolower((string) $text, 'UTF-8'));
        $len = mb_strlen($text, 'UTF-8');

        if ($len < 2) {
            return '' === $text ? array() : array($text);
        } $grams = array();
        for ($i = 0; $i < $len - 1; $i++) {
            $grams[] = mb_substr($text, $i, 2, 'UTF-8');
        }

return $grams;
    }
}
