<?php

namespace App\Livewire;

use App\Enums\ActivityTypeEnum;
use App\Enums\ApprovalStatusEnum;
use App\Events\ActivityLogged;
use App\Jobs\ArticleDiscoveryJob;
use App\Models\Feed;
use App\Models\RouteArticle;
use App\Support\PendingFeedGroup;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Livewire\Component;
use Livewire\WithPagination;

class Articles extends Component
{
    use WithPagination;

    public string $tab = 'pending';

    public string $search = '';

    public ?int $feedId = null;

    public bool $isRefreshing = false;

    /** @var array<int, bool> */
    public array $expandedFeeds = [];

    public function setTab(string $tab): void
    {
        $this->tab = $tab;
        $this->search = '';
        $this->expandedFeeds = [];
        $this->resetPage();
    }

    public function updatedFeedId(): void
    {
        $this->expandedFeeds = [];
        $this->resetPage();
    }

    public function toggleFeed(int $feedId): void
    {
        if (isset($this->expandedFeeds[$feedId])) {
            unset($this->expandedFeeds[$feedId]);

            return;
        }

        $this->expandedFeeds[$feedId] = true;
    }

    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function approve(int $routeArticleId): void
    {
        RouteArticle::findOrFail($routeArticleId)->approve();
    }

    public function reject(int $routeArticleId): void
    {
        RouteArticle::findOrFail($routeArticleId)->reject();
    }

    public function restore(int $routeArticleId): void
    {
        $routeArticle = RouteArticle::findOrFail($routeArticleId);
        $routeArticle->update([
            'approval_status' => ApprovalStatusEnum::PENDING,
            'decided_at' => null,
        ]);
    }

    public function clear(): void
    {
        $feed = $this->feedId !== null ? Feed::find($this->feedId) : null;
        $cleared = $this->clearableQuery()->update([
            'approval_status' => ApprovalStatusEnum::REJECTED,
            'decided_at' => now(),
        ]);

        if ($cleared > 0) {
            ActivityLogged::dispatch(
                ActivityTypeEnum::REJECT,
                $feed !== null
                    ? "Cleared {$cleared} pending articles from {$feed->name}"
                    : "Cleared {$cleared} pending articles",
                ['cleared' => $cleared],
                $feed,
            );
        }

        $this->expandedFeeds = [];
    }

    /**
     * @return Builder<RouteArticle>
     */
    private function clearableQuery(): Builder
    {
        return RouteArticle::query()
            ->where('approval_status', ApprovalStatusEnum::PENDING)
            ->when($this->feedId !== null, fn (Builder $q) => $q->where('feed_id', $this->feedId));
    }

    public function refresh(): void
    {
        $this->isRefreshing = true;

        ArticleDiscoveryJob::dispatch();

        $this->dispatch('refresh-started');
    }

    public function retryPublish(int $routeArticleId): void
    {
        $routeArticle = RouteArticle::failed()->find($routeArticleId);

        if (! $routeArticle instanceof RouteArticle) {
            return;
        }

        $routeArticle->clearPublishFailure();

        ActivityLogged::dispatch(
            ActivityTypeEnum::PUBLISH,
            "Queued \"{$routeArticle->article->title}\" for another publish attempt",
            ['route_article_id' => $routeArticle->id],
            $routeArticle->article,
        );
    }

    public function render(): View
    {
        $pendingCount = RouteArticle::where('approval_status', ApprovalStatusEnum::PENDING)->count();
        $failedCount = RouteArticle::failed()->count();

        if ($this->tab === 'pending') {
            return view('livewire.articles', [
                'routeArticles' => null,
                'pendingFeeds' => $this->pendingFeeds(),
                'feedOptions' => $this->feedOptions(),
                'pendingCount' => $pendingCount,
                'failedCount' => $failedCount,
                'clearableCount' => $this->clearableQuery()->count(),
            ])->layout('layouts.app');
        }

        $query = RouteArticle::with(['article.feed', 'feed', 'platformChannel'])
            ->orderBy('created_at', 'desc');

        if ($this->tab === 'failed') {
            $query->failed();
        }

        if ($this->feedId !== null) {
            $query->where('feed_id', $this->feedId);
        }

        if ($this->search !== '') {
            $search = $this->search;
            $query->whereHas('article', function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                    ->orWhere('description', 'like', "%{$search}%");
            });
        }

        return view('livewire.articles', [
            'routeArticles' => $query->paginate(15),
            'pendingFeeds' => null,
            'feedOptions' => $this->feedOptions(),
            'pendingCount' => $pendingCount,
            'failedCount' => $failedCount,
            'clearableCount' => 0,
        ])->layout('layouts.app');
    }

    /**
     * @return Collection<int, PendingFeedGroup>
     */
    private function pendingFeeds(): Collection
    {
        $counts = RouteArticle::query()
            ->selectRaw('feed_id, COUNT(*) as aggregate')
            ->where('approval_status', ApprovalStatusEnum::PENDING)
            ->when($this->feedId !== null, fn ($q) => $q->where('feed_id', $this->feedId))
            ->groupBy('feed_id')
            ->pluck('aggregate', 'feed_id');

        return Feed::whereIn('id', $counts->keys())->get()
            ->map(fn (Feed $feed): PendingFeedGroup => new PendingFeedGroup(
                $feed,
                (int) $counts->get($feed->id, 0),
                isset($this->expandedFeeds[$feed->id])
                    ? $this->routeArticlesForFeed($feed->id)
                    : null,
            ))
            ->sortByDesc('count')
            ->values();
    }

    /**
     * @return EloquentCollection<int, Feed>
     */
    private function feedOptions(): EloquentCollection
    {
        return Feed::whereIn('id', RouteArticle::query()->select('feed_id')->distinct())
            ->orderBy('name')
            ->get();
    }

    /**
     * @return EloquentCollection<int, RouteArticle>
     */
    private function routeArticlesForFeed(int $feedId): EloquentCollection
    {
        return RouteArticle::with(['article.feed', 'feed', 'platformChannel'])
            ->where('approval_status', ApprovalStatusEnum::PENDING)
            ->where('feed_id', $feedId)
            ->orderBy('created_at', 'desc')
            ->get();
    }
}
