<?php

namespace App\Traits;

use App\Helpers\StringHelper;
use App\Models\Category;
use App\Models\CategoryTranslation;
use App\Models\TaggableLocale;
use Cviebrock\EloquentTaggable\Events\ModelTagged;
use Cviebrock\EloquentTaggable\Events\ModelUntagged;
use Cviebrock\EloquentTaggable\Exceptions\NoTagsSpecifiedException;
use Cviebrock\EloquentTaggable\Models\Tag;
use Cviebrock\EloquentTaggable\Services\TagService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;

/**
 * Class TaggableWithLocale
 *
 * Is a customized copy of:
 * @package Cviebrock\EloquentTaggable 9.0
 *
 * This trait includes modified Taggable methods to use also the extra table tag_contexts,
 * which is not included in the original Taggable package.
 */

trait TaggableWithLocale
{
    public function translateTagName($tagName, $fromLocale, $toLocale)
    {
        $tagName =  mb_strtolower($tagName);
        $result = Tag::where('name', $tagName)
            ->whereHas('locale', function ($query) use ($fromLocale) {
                $query->where('locale', $fromLocale);
            })
        ->with(
            'contexts.tags',
            function ($q) use ($toLocale) {
                $q->whereHas('locale', function ($q) use ($toLocale) {
                    $q->where('locale', $toLocale);
                })->select('normalized');
            }
        )
        ->get();

        if ($result->count() != 0) {

            $result = $result->first()
            ->contexts;

            if ($result->first()) {
                $result = $result
                        ->first()
                        ->tags
                        ->pluck('normalized')
                        ->unique()
                        ->values()
                        ->reject($tagName)
                        ->flatten()
                ;
            } else {
                $result = [];
            }
        } else {
            $result = [];
        }
        return $result;
    }


    //! TODO conditionally select fallback or source locale, similar method as in translateTagIdsWithContexts()
    public function translateTagNameWithContext($name = null, $toLocale = null)
    {
        if (!$toLocale) {
            $toFallbackLocale = app()->getFallBackLocale();
        }

        if (!$toFallbackLocale) {
            $toFallbackLocale = app()->getFallBackLocale();
        }

        $result = Tag::where('name', $name)
            ->with([
                'contexts.tags' => function ($query) use ($toLocale) {
                    $query->with(['locale' => function ($query) use ($toLocale) {
                        $query->where('locale', $toLocale)->select('taggable_tag_id', 'locale');
                    }])
                    ->whereHas('locale', function ($query) use ($toLocale) {
                        $query->where('locale', $toLocale);
                    })
                    ->select('taggable_tags.tag_id', 'normalized');
                },
                'contexts.category' => function ($query) use ($toLocale) {
                    $query->with(['translations' => function ($query) use ($toLocale) {
                        $query->where('locale', $toLocale)->select('category_id', 'name');
                    }])
                    ->select('id');
                },
            ])
            ->get();


        if ($result->count() != 0) {

            $result = $result->first()
            ->contexts;

            if ($result->first()) {

                $tag = $result
                        ->first()
                        ->tags
                        ->unique()
                        ->values()
                        ->flatten();
                $category = $result
                        ->first()
                        ->category
                        ->translations
                        ->first();
                $categoryPath = $result
                        ->first()
                        ->category
                        ->ancestorsAndSelf
                        ->sortBy('id')
                        ->pluck('id');

                $result = $tag->map(function ($item) use ($category, $categoryPath, $toLocale) {
                    $localeItem = $item->locale ? $item->locale : null;
                    $mapped = [
                        'tag_id' => $item->tag_id,
                        'tag' => StringHelper::dutchTitleCase($item->normalized),
                        'category_id' => $category->category_id,
                        'category' => StringHelper::dutchTitleCase($item->normalized),
                        'category_path' => implode(
                            ' > ',
                            CategoryTranslation::whereIn('category_id', $categoryPath)->where('locale', $toLocale)->pluck('name')->toArray()
                        ) . ' > ' . StringHelper::dutchTitleCase($item->normalized),
                        'locale' => $localeItem->find($item->tag_id)
                        ];
                    return $mapped;
                });

            } else {
                $result = [];
            }
        } else {
            $result = [];
        }
        return $result;
    }


    //! TODO conditionally select fallback or source locale, similar method as in translateTagIdsWithContexts()
    public function translateTagNamesWithContexts($array, $toLocale = null, $toFallbackLocale = null)
    {

        if (!$toLocale) {
            $toFallbackLocale = app()->getFallBackLocale();
        }

        if (!$toFallbackLocale) {
            $toFallbackLocale = app()->getFallBackLocale();
        }

        $collection = collect($array);
        $translated = $collection->map(function ($item, $key) use ($toLocale, $toFallbackLocale) {

            $source = $item;

            $transLocale = $this->translateTagNameWithContext($source, $toLocale);
            $transFallbackLocale = $this->translateTagNameWithContext($source, $toFallbackLocale);

            if ($transLocale === $source) {
                return  $source;
            } elseif (count($transLocale) > 0) {
                return  $transLocale;
            } elseif (count($transFallbackLocale) > 0) {
                return  $transFallbackLocale;
            } else {
                return $source;
            }
        })
        ->flatMap(function ($innerCollection) {
            return $innerCollection;
        });

        return $translated;
    }


    public function translateTagId($tagId, $toLocale)
    {
        $result = Tag::where('tag_id', $tagId)
        ->with(
            'contexts.tags',
            function ($q) use ($toLocale) {
                $q->whereHas('locale', function ($q) use ($toLocale) {
                    $q->where('locale', $toLocale);
                })->select('normalized');
            }
        )
        ->get();

        if ($result->count() != 0) {

            $result = $result->first()
            ->contexts;

            if ($result->first()) {
                $result = $result
                        ->first()
                        ->tags
                        ->pluck('pivot')
                        ->pluck('tag_id')
                        ->unique()
                        ->values()
                        ->flatten()
                ;
            } else {
                $result = [];
            }
        } else {
            $result = [];
        }
        return $result;
    }


    public function translateTagIdWithContext($tagId)
    {
        if ($tagId) {
            $sourceLocale = TaggableLocale::where('taggable_tag_id', $tagId)->value('locale');
            $tag = Tag::find($tagId);
            $translatedTag = $tag->translation;
            $category = Category::find($tag->contexts->pluck('category_id')->first());
            $translatedCategory = $category->translation ?? '';
            if ($translatedCategory) {
                        $categoryPathIds = $category->ancestorsAndSelf->sortBy('id')->pluck('id');
                $categoryPath = implode(
                    ' > ',
                    CategoryTranslation::whereIn('category_id', $categoryPathIds)
                                    ->where('locale', App::getLocale())
                                    ->pluck('name')
                                    ->toArray()
                );
                $categoryColor = $category->rootAncestor ? $category->rootAncestor->color : $category->color;
            }
            // Map and return the finalized result
            return [
                'original_tag_id' => $tagId,
                'tag_id' => $translatedTag->tag_id,
                'tag' => $translatedTag->name,
                'comment' => $translatedTag->comment,
                'locale' => $translatedTag->locale,
                'category_id' => $category->id ?? null,
                'category' => $translatedCategory->name ?? '',
                'category_path' => $categoryPath ?? '',
                'category_color' => $categoryColor ?? 'gray',
            ];
        } else {
            return false;
        }
    }


    public function translateTagIds($array, $toLocale = null, $toFallbackLocale = null)
    {
        if (!$toLocale) {
            $toFallbackLocale = app()->getFallBackLocale();
        }

        if (!$toFallbackLocale) {
            $toFallbackLocale = app()->getFallBackLocale();
        }

        $collection = collect($array);
        $translated = $collection->map(function ($item, $key) use ($toLocale, $toFallbackLocale) {

            $source = $item;

            $transLocale = $this->translateTagId($source, $toLocale);
            $transFallbackLocale = $this->translateTagId($source, $toFallbackLocale);

            if ($transLocale === $source) {
                return  $source;
            } elseif (count($transLocale) > 0) {
                return  $transLocale;
            } elseif (count($transFallbackLocale) > 0) {
                return  $transFallbackLocale;
            } else {
                return $source;
            }
        })->flatten()->toArray();

        return $translated;
    }



    public function translateTagIdsWithContexts($tagIds)
    {
        $collectionIds = collect($tagIds);
        $translated = $collectionIds->map(function ($item) {
            $item = $this->translateTagIdWithContext($item);
            return $item;
        });

        return $translated;
    }

    /**
     * Get an array of normalized tags for a given locale.
     *
     * @param string $locale The locale to get tags for.
     *
     * @return array An array of normalized tags for the given locale.
     */
    public function localTagArray($locale)
    {
        $array = Tag::whereHas('locale', function ($query) use ($locale) {
            $query->where('locale', $locale);
        })->pluck('name')->toArray();
        return $array;
    }


    /**
     * Clean up duplicate taggables with different locales
     * and clean up any orphaned taggables
     * Example Usage: 
     * $user = User::find(161)->cleantaggables(); 
     *
     * @return mixed The result of the untagging operation.
     */
    public function cleanTaggables()
    {   
        $this->cleanForeignTaggables();
        $this->cleanOrhphanedTaggables();

        return true;
    }


    public function cleanForeignTaggables()
    {
        // Get the tags with their contexts
        $tagsWithContexts = $this->tags()->with('localeContext')->get();
        $tagIds = $tagsWithContexts->pluck('tag_id');
        $contextIds = $tagsWithContexts->pluck('localeContext.context_id')->flatten();

        // Find the context IDs that appear more than once
        $duplicateContextIds = $contextIds->duplicates()->flatten();

        $duplicateTagIds = DB::table('taggable_locale_context')
            ->whereIn('context_id', $duplicateContextIds)
            ->whereIn('tag_id', $tagIds)
            ->pluck('tag_id')
            ->flatten();

        $duplicateTagsIdsForeign = DB::table('taggable_locales')
            ->where('locale', '!=', App::getLocale())
            ->whereIn('taggable_tag_id', $duplicateTagIds)
            ->pluck('taggable_tag_id');

        $orhpanedTagIds = DB::table('taggable_taggables')
            ->leftJoin('taggable_tags', 'taggable_taggables.tag_id', '=', 'taggable_tags.tag_id')
            ->whereNull('taggable_tags.tag_id')
            ->select('taggable_taggables.tag_id')
            ->distinct()
            ->pluck('tag_id');

        return $this->untagById($duplicateTagsIdsForeign);
    }

    
    Public function cleanOrhphanedTaggables()
    {        
        $orhpanedTagIds = DB::table('taggable_taggables')
            ->leftJoin('taggable_tags', 'taggable_taggables.tag_id', '=', 'taggable_tags.tag_id')
            ->whereNull('taggable_tags.tag_id')
            ->select('taggable_taggables.tag_id')
            ->distinct()
            ->pluck('tag_id');

        return  $this->tags()->detach($orhpanedTagIds);
    }


    /**
     * Returns a string of normalized tags for the given locale.
     *
     * @param string $locale The locale to filter tags by.
     * @return string The string of normalized tags.
     */
    public function localTagList($locale)
    {
        $array = Tag::whereHas('locale', function ($query) use ($locale) {
            $query->where('locale', $locale);
        })->pluck('normalized')->toArray();
        return implode(config('taggable.glue'), $array);
    }


    /**
     * Find the tag with the given name.
     *
     * @param string $value
     *
     * @return static|null
     */
    public static function findByName(string $value)
    {
        return app(TagService::class)->find($value);
    }



    /**
     * Property to control sequence on alias
     *
     * @var int
     */
    private $taggableAliasSequence = 0;


    /**
     * Boot the trait.
     *
     * Listen for the deleting event of a model, then remove the relation between it and tags
     */
    protected static function bootTaggable(): void
    {
        static::deleting(function ($model) {
            if (!method_exists($model, 'runSoftDelete') || $model->isForceDeleting()) {
                $model->detag();
            }
        });
    }


    /**
     * Get a collection of all tags the model has.
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
     */
    public function tags(): MorphToMany
    {
        $model = config('taggable.model');
        $table = config('taggable.tables.taggable_taggables', 'taggable_taggables');
        return $this->morphToMany($model, 'taggable', $table, 'taggable_id', 'tag_id')
            ->withTimestamps();
    }



    /**
     * Attach one or multiple tags to the model.
     *
     * @param string|array $tags
     *
     * @return self
     */
    public function tag($tags): self
    {
        $tags = app(TagService::class)->buildTagArray($tags);

        foreach ($tags as $tagName) {
            $this->addOneTag($tagName);
            $this->load('tags');
        }

        event(new ModelTagged($this, $tags));

        return $this;
    }

    /**
     * Attach one or more existing tags to a model,
     * identified by the tag's IDs.
     *
     * @param int|int[] $ids
     *
     * @return $this
     */
    public function tagById($ids): self
    {
        $tags = app(TagService::class)->findByIds($ids);
        $names = $tags->pluck('name')->all();

        return $this->tag($names);
    }

    /**
     * Detach one or multiple tags from the model.
     *
     * @param string|array $tags
     *
     * @return self
     */
    public function untag($tags): self
    {
        $tags = app(TagService::class)->buildTagArray($tags);

        foreach ($tags as $tagName) {
            $this->removeOneTag($tagName);
        }

        event(new ModelUntagged($this, $tags));

        return $this->load('tags');
    }

    /**
     * Detach one or more existing tags to a model,
     * identified by the tag's IDs.
     *
     * @param int|int[] $ids
     *
     * @return $this
     */
    public function untagById($ids): self
    {
        $tags = app(TagService::class)->findByIds($ids);
        $names = $tags->pluck('name')->all();

        return $this->untag($names);
    }


    /**
     * Remove all tags from the model and assign the given ones.
     *
     * @param string|array $tags
     *
     * @return self
     */
    public function retag($tags): self
    {
        return $this->detag()->tag($tags);
    }

    /**
     * Remove all tags from the model and assign the given ones by ID.
     *
     * @param int|int[] $ids
     *
     * @return self
     */
    public function retagById($ids): self
    {
        return $this->detag()->tagById($ids);
    }


    /**
     * Remove all tags from the model.
     *
     * @return self
     */
    public function detag(): self
    {
        $this->tags()->sync([]);

        return $this->load('tags');
    }


    /**
     * Add one tag to the model.
     *
     * @param string $tagName
     */
    protected function addOneTag(string $tagName): void
    {
        /** @var Tag $tag */
        $tag = app(TagService::class)->findOrCreate($tagName);
        $tagKey = $tag->getKey();
        if (!$this->getAttribute('tags')->contains($tagKey)) {
            $this->tags()->attach($tagKey);
        }

        $locale = ['locale' => App::getLocale()];      // Customization: include App Locale when adding a tag
        TaggableLocale::updateOrCreate(['taggable_tag_id' => $tag->getKey()], $locale);    // Customization: include App Locale when adding a tag
    }

    /**
     * Remove one tag from the model
     *
     * @param string $tagName
     */
    protected function removeOneTag(string $tagName): void
    {
        $tag = app(TagService::class)->find($tagName);

        if ($tag) {
            $this->tags()->detach($tag);
        }
    }

    /**
     * Get all the tags of the model as a delimited string.
     *
     * @return string
     */
    public function getTagListAttribute(): string
    {
        return app(TagService::class)->makeTagList($this);
    }

    /**
     * Get all normalized tags of a model as a delimited string.
     *
     * @return string
     */
    public function getTagListNormalizedAttribute(): string
    {
        return app(TagService::class)->makeTagList($this, 'normalized');
    }

    /**
     * Get all tags of a model as an array.
     *
     * @return array
     */
    public function getTagArrayAttribute(): array
    {
        return app(TagService::class)->makeTagArray($this);
    }

    /**
     * Get all normalized tags of a model as an array.
     *
     * @return array
     */
    public function getTagArrayNormalizedAttribute(): array
    {
        return app(TagService::class)->makeTagArray($this, 'normalized');
    }


    /**
     * Determine if a given tag is attached to the model.
     *
     * @param Tag|string $tag
     *
     * @return bool
     */
    public function hasTag($tag): bool
    {
        if ($tag instanceof Tag) {
            $normalized = $tag->getAttribute('normalized');
        } else {
            $normalized = app(TagService::class)->normalize($tag);
        }

        return in_array($normalized, $this->getTagArrayNormalizedAttribute(), true);
    }


    /**
     * Query scope for models that have all of the given tags.
     *
     * @param Builder $query
     * @param array|string $tags
     *
     * @return Builder
     * @throws \Cviebrock\EloquentTaggable\Exceptions\NoTagsSpecifiedException
     * @throws \ErrorException
     */
    public function scopeWithAllTags(Builder $query, $tags): Builder
    {
        /** @var TagService $service */
        $service = app(TagService::class);
        $normalized = $service->buildTagArrayNormalized($tags);

        // If there are no tags specified, then there
        // can't be any results so short-circuit
        if (count($normalized) === 0) {
            if (config('taggable.throwEmptyExceptions')) {
                throw new NoTagsSpecifiedException('Empty tag data passed to withAllTags scope.');
            }

            return $query->where(\DB::raw(1), 0);
        }

        $tagKeys = $service->getTagModelKeys($normalized);

        // If some of the tags specified don't exist, then there can't
        // be any models with all the tags, so so short-circuit
        if (count($tagKeys) !== count($normalized)) {
            return $query->where(\DB::raw(1), 0);
        }

        $alias = $this->taggableCreateNewAlias(__FUNCTION__);
        $morphTagKeyName = $this->getQualifiedRelatedPivotKeyNameWithAlias($alias);

        return $this->prepareTableJoin($query, 'inner', $alias)
            ->whereIn($morphTagKeyName, $tagKeys)
            ->havingRaw("COUNT(DISTINCT {$morphTagKeyName}) = ?", [count($tagKeys)]);
    }

    /**
     * Query scope for models that have any of the given tags.
     *
     * @param Builder $query
     * @param array|string $tags
     *
     * @return Builder
     * @throws \Cviebrock\EloquentTaggable\Exceptions\NoTagsSpecifiedException
     * @throws \ErrorException
     */
    public function scopeWithAnyTags(Builder $query, $tags): Builder
    {
        /** @var TagService $service */
        $service = app(TagService::class);
        $normalized = $service->buildTagArrayNormalized($tags);

        // If there are no tags specified, then there is
        // no filtering to be done so short-circuit
        if (count($normalized) === 0) {
            if (config('taggable.throwEmptyExceptions')) {
                throw new NoTagsSpecifiedException('Empty tag data passed to withAnyTags scope.');
            }

            return $query->where(\DB::raw(1), 0);
        }

        $tagKeys = $service->getTagModelKeys($normalized);

        $alias = $this->taggableCreateNewAlias(__FUNCTION__);
        $morphTagKeyName = $this->getQualifiedRelatedPivotKeyNameWithAlias($alias);

        return $this->prepareTableJoin($query, 'inner', $alias)
            ->whereIn($morphTagKeyName, $tagKeys);
    }

    /**
     * Query scope for models that have any tag.
     *
     * @param Builder $query
     *
     * @return Builder
     */
    public function scopeIsTagged(Builder $query): Builder
    {
        $alias = $this->taggableCreateNewAlias(__FUNCTION__);
        return $this->prepareTableJoin($query, 'inner', $alias);
    }

    /**
     * Query scope for models that do not have all of the given tags.
     *
     * @param Builder $query
     * @param string|array $tags
     * @param bool $includeUntagged
     *
     * @return Builder
     * @throws \ErrorException
     */
    public function scopeWithoutAllTags(Builder $query, $tags, bool $includeUntagged = false): Builder
    {
        /** @var TagService $service */
        $service = app(TagService::class);
        $normalized = $service->buildTagArrayNormalized($tags);
        $tagKeys = $service->getTagModelKeys($normalized);
        $tagKeyList = implode(',', $tagKeys);

        $alias = $this->taggableCreateNewAlias(__FUNCTION__);
        $morphTagKeyName = $this->getQualifiedRelatedPivotKeyNameWithAlias($alias);

        $query = $this->prepareTableJoin($query, 'left', $alias)
            ->havingRaw(
                "COUNT(DISTINCT CASE WHEN ({$morphTagKeyName} IN ({$tagKeyList})) THEN {$morphTagKeyName} ELSE NULL END) < ?",
                [count($tagKeys)]
            );

        if (!$includeUntagged) {
            $query->havingRaw("COUNT(DISTINCT {$morphTagKeyName}) > 0");
        }

        return $query;
    }

    /**
     * Query scope for models that do not have any of the given tags.
     *
     * @param Builder $query
     * @param string|array $tags
     * @param bool $includeUntagged
     *
     * @return Builder
     * @throws \ErrorException
     */
    public function scopeWithoutAnyTags(Builder $query, $tags, bool $includeUntagged = false): Builder
    {
        /** @var TagService $service */
        $service = app(TagService::class);
        $normalized = $service->buildTagArrayNormalized($tags);
        $tagKeys = $service->getTagModelKeys($normalized);
        $tagKeyList = implode(',', $tagKeys);

        $alias = $this->taggableCreateNewAlias(__FUNCTION__);
        $morphTagKeyName = $this->getQualifiedRelatedPivotKeyNameWithAlias($alias);

        $query = $this->prepareTableJoin($query, 'left', $alias)
            ->havingRaw("COUNT(DISTINCT CASE WHEN ({$morphTagKeyName} IN ({$tagKeyList})) THEN {$morphTagKeyName} ELSE NULL END) = 0");

        if (!$includeUntagged) {
            $query->havingRaw("COUNT(DISTINCT {$morphTagKeyName}) > 0");
        }

        return $query;
    }

    /**
     * Query scope for models that does not have have any tags.
     *
     * @param Builder $query
     *
     * @return Builder
     */
    public function scopeIsNotTagged(Builder $query): Builder
    {
        $alias = $this->taggableCreateNewAlias(__FUNCTION__);
        $morphForeignKeyName = $this->getQualifiedForeignPivotKeyNameWithAlias($alias);

        return $this->prepareTableJoin($query, 'left', $alias)
            ->havingRaw("COUNT(DISTINCT {$morphForeignKeyName}) = 0");
    }

    /**
     * @param Builder $query
     * @param string $joinType
     *
     * @return Builder
     */
    private function prepareTableJoin(Builder $query, string $joinType, string $alias): Builder
    {
        $morphTable = $this->tags()->getTable();
        $morphTableAlias = $morphTable.'_'.$alias;

        $modelKeyName = $this->getQualifiedKeyName();
        $morphForeignKeyName = $this->getQualifiedForeignPivotKeyNameWithAlias($alias);

        $morphTypeName = $morphTableAlias.'.'. $this->tags()->getMorphType();
        $morphClass = $this->tags()->getMorphClass();

        $closure = function (JoinClause $join) use ($modelKeyName, $morphForeignKeyName, $morphTypeName, $morphClass) {
            $join->on($modelKeyName, $morphForeignKeyName)
                ->where($morphTypeName, $morphClass);
        };

        return $query
            ->select($this->getTable() . '.*')
            ->join($morphTable.' as '.$morphTableAlias, $closure, null, null, $joinType)
            ->groupBy($modelKeyName);
    }

    /**
     * Get a collection of all the tag models used for the called class.
     *
     * @return Collection
     */
    public static function allTagModels(): Collection
    {
        return app(TagService::class)->getAllTags(static::class);
    }

    /**
     * Get an array of all tags used for the called class.
     *
     * @return array
     */
    public static function allTags(): array
    {
        /** @var \Illuminate\Database\Eloquent\Collection $tags */
        $tags = static::allTagModels();

        return $tags->pluck('name')->sort()->all();
    }

    /**
     * Get all the tags used for the called class as a delimited string.
     *
     * @return string
     */
    public static function allTagsList(): string
    {
        return app(TagService::class)->joinList(static::allTags());
    }

    /**
     * Rename one the tags for the called class.
     *
     * @param string $oldTag
     * @param string $newTag
     *
     * @return int
     */
    public static function renameTag(string $oldTag, string $newTag): int
    {
        return app(TagService::class)->renameTags($oldTag, $newTag, static::class);
    }

    /**
     * Get the most popular tags for the called class.
     *
     * @param int $limit
     * @param int $minCount
     *
     * @return array
     */
    public static function popularTags(int $limit = null, int $minCount = 1): array
    {
        /** @var Collection $tags */
        $tags = app(TagService::class)->getPopularTags($limit, static::class, $minCount);

        return $tags->pluck('taggable_count', 'name')->all();
    }

    /**
     * Get the most popular tags for the called class.
     *
     * @param int $limit
     * @param int $minCount
     *
     * @return array
     */
    public static function popularTagsNormalized(int $limit = null, int $minCount = 1): array
    {
        /** @var Collection $tags */
        $tags = app(TagService::class)->getPopularTags($limit, static::class, $minCount);

        return $tags->pluck('taggable_count', 'normalized')->all();
    }

    /**
     * Returns the Related Pivot Key Name with the table alias.
     *
     * @param string $alias
     *
     * @return string
     */
    private function getQualifiedRelatedPivotKeyNameWithAlias(string $alias): string
    {
        $morph = $this->tags();

        return $morph->getTable() . '_' . $alias .
            '.' . $morph->getRelatedPivotKeyName();
    }

    /**
     * Returns the Foreign Pivot Key Name with the table alias.
     *
     * @param string $alias
     *
     * @return string
     */
    private function getQualifiedForeignPivotKeyNameWithAlias(string $alias): string
    {
        $morph = $this->tags();

        return $morph->getTable() . '_' . $alias .
            '.' . $morph->getForeignPivotKeyName();
    }

    /**
     * Create a new alias to use on scopes to be able to combine many scopes
     *
     * @param string $scope
     *
     * @return string
     */
    private function taggableCreateNewAlias(string $scope): string
    {
        $this->taggableAliasSequence++;
        $alias = strtolower($scope) . '_' . $this->taggableAliasSequence;

        return $alias;
    }
}
