<?php

namespace App\Traits;

use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Carbon\Carbon;

use App\Models\Tag;
use App\Models\Blog;
use App\Models\Course;
use App\Models\Announcement;
use App\Models\Version;

use App\Utilities\Paginate;

trait FindByTagsTrait
{
    public function loadCollectionAttributes(Collection $items)
    {
        if (!$items->count()) {
            return $items;
        }

        return $items->load('publishedVersion', 'contentElements.content.photos.fileUpload', 'tags', 'version')
                               ->each(function ($item) {
                                   $item->append('content', 'name', 'full_slug', 'actions', 'first_published_at');
                                   $item->content->each(function ($content_element) {
                                       $content_element->content->appendAttributes();
                                   });
                               });
    }

    public function getByTags($input = null, $ids = null, $paginate = false)
    {
        $tags = collect();
        $exclude_tags = collect();
        $can_view_any = auth()->user()?->can('viewAny', $this);

        $mode = Arr::get($input, 'tag_mode', 'or');

        if (Arr::get($input, 'tags')) {
            $include_ids = collect(Arr::get($input, 'tags'))->filter(function ($tag) {
                if (Arr::get($tag, 'pivot.exclude')) {
                    return false;
                }
                return true;
            })->pluck('id')->toArray();

            $exclude_ids = collect(Arr::get($input, 'tags'))->filter(function ($tag) {
                if (Arr::get($tag, 'pivot.exclude')) {
                    return true;
                }
                return false;
            })->pluck('id')->toArray();

            $tags = Tag::whereIn('tags.id', $include_ids)->get();
            /*
            $tags = Tag::whereIn('tags.id', $include_ids)->get()->map(function($tag) {
                return collect([ $tag ])->merge($tag->tags);
            })->flatten();
             */
            $exclude_tags = Tag::whereIn('tags.id', $exclude_ids)->get();
        }

        $collection = $this->where(function ($query) use ($ids, $tags, $exclude_tags, $input, $mode, $can_view_any) {
            if ($ids) {
                $query->whereIn($this->getTable().'.id', $ids);
            }

            if (Arr::get($input, 'exclude_id')) {
                $query->where($this->getTable().'.id', '!=', Arr::get($input, 'exclude_id'));
            }

            if (Arr::get($input, 'exclude_ids')) {
                $query->whereNotIn($this->getTable().'.id', Arr::get($input, 'exclude_ids'));
            }

            if ($tags->count()) {
                if ($mode === 'and') {
                    foreach ($tags as $tag) {
                        $query->whereHas('tags', function ($query) use ($tag) {
                            $query->where('tags.id', $tag->id);
                        });
                    }
                } elseif ($mode === 'or') {
                    $query->whereHas('tags', function ($query) use ($tags) {
                        $query->whereIn('tags.id', $tags->pluck('id')->toArray());
                    });
                }
            }

            if ($exclude_tags->count()) {
                $query->whereDoesntHave('tags', function ($query) use ($exclude_tags) {
                    $query->whereIn('tags.id', $exclude_tags->pluck('id')->toArray());
                });
            }

            if ((!editing() && !$can_view_any && ($this instanceof Blog || $this instanceof Course || $this instanceof Announcement)) || Arr::get($input, 'hide_unlisted')) {
                $query->whereHas('version', function ($query) {
                    $query->where('unlisted', false);
                });
            }
        });

        $has_versioning = collect(class_uses($this))->contains('App\Traits\VersioningTrait');

        if ($has_versioning) {
            if (!$can_view_any) {
                $collection->whereHas('version', function ($query) {
                    $query->where('signed_url', 0);
                });
            }
        }

        if (Arr::get($input, 'deleted')) {
            $collection->withTrashed();
        }

        $collection->with('tags');

        if (Arr::get($input, 'limit')) {
           $collection ->limit(Arr::get($input, 'limit'));
        }

        $sort_by = Arr::get($input, 'sort_by') ?? 'id';
        $sort_direction = Arr::get($input, 'descending') ? 'desc' : 'asc';


        if ($sort_by) {
            if (Schema::hasColumn($this->getTable(), $sort_by)) {
                $collection->orderBy($sort_by, $sort_direction);
            } elseif ($has_versioning) {
                if (collect(Schema::getColumnListing((new Version())->getTable()))->contains($sort_by)) {
                    $collection->orderBy(
                        Version::select($sort_by)
                        ->whereColumn('versions.versionable_id', $this->getTable().'.id')
                        ->where('versions.versionable_type', get_class($this))
                        ->where(function ($query) use ($can_view_any) {
                            if (!$can_view_any) {
                                $query->whereNotNull('versions.published_at');
                            }
                        })
                        ->orderBy('id', 'desc')
                        ->take(1),
                        $sort_direction
                    );
                }
            } else {
                abort(404, $sort_by.' column not found');
            }
        }

        if ($paginate) {

            $paginate_count = Arr::get($input, 'paginate_count');

            if (!is_numeric($paginate_count)) {
                $paginate_count = 10;
            }

            if ($paginate_count > 100) {
                $paginate_count = 100;
            }

            $paginator = $collection->paginate($paginate_count, $columns = ['*'], $pageName = 'paginate_page');
            $items = $paginator->getCollection();
            $items = $this->loadCollectionAttributes($items);
            $paginator->setCollection($items);
            return $paginator;
        } else {
            return $this->loadCollectionAttributes($collection->get());
        }
    }

    public function paginateByTags($input = null, $ids = null)
    {
        return $this->getByTags($input, $ids, true);
    }
}
