<?php

namespace App\Livewire;

use App\Dashboard\Stats\ApprovalRate;
use App\Dashboard\Stats\ArticlesPerFeed;
use App\Dashboard\Stats\ArticlesTrend;
use App\Dashboard\Stats\BreakdownResult;
use App\Dashboard\Stats\BreakdownStat;
use App\Dashboard\Stats\PublicationsPerChannel;
use App\Dashboard\Stats\PublishSuccessRate;
use App\Dashboard\Stats\SeriesResult;
use App\Models\ActivityLog;
use App\Services\DashboardStatsService;
use App\Support\DateRange;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Carbon;
use InvalidArgumentException;
use Livewire\Attributes\Computed;
use Livewire\Component;

class Dashboard extends Component
{
    public string $from = '';

    public string $to = '';

    public function mount(): void
    {
        $this->applyPreset('month');
    }

    /**
     * Islands whose content depends on the global range; skipped islands never re-render on their own.
     *
     * @var array<int, string>
     */
    private const RANGE_DEPENDENT_ISLANDS = [
        'article-statistics',
        'articles-trend',
        'approval-rate',
        'publish-success-rate',
        'articles-per-feed',
        'publications-per-channel',
    ];

    private const RECENT_ACTIVITY_LIMIT = 5;

    public function applyPreset(string $preset): void
    {
        try {
            $range = DateRange::preset($preset);
        } catch (InvalidArgumentException) {
            return;
        }

        $this->applyRange($range->from->toDateString(), $range->to->toDateString());
    }

    public function applyRange(string $from, string $to): void
    {
        $this->from = $from;
        $this->to = $to;

        $this->validateRange();
        $this->refreshRangeDependentIslands();
    }

    private function refreshRangeDependentIslands(): void
    {
        foreach (self::RANGE_DEPENDENT_ISLANDS as $island) {
            $this->renderIsland(name: $island);
        }
    }

    /**
     * @return array<string, string>
     */
    #[Computed]
    public function presets(): array
    {
        return DateRange::presets();
    }

    public bool $rangeIsValid = true;

    public function range(): ?DateRange
    {
        $from = $this->parseBoundary($this->from);
        $to = $this->parseBoundary($this->to);

        if (! $from instanceof Carbon || ! $to instanceof Carbon) {
            return null;
        }

        try {
            return new DateRange($from->startOfDay(), $to->endOfDay());
        } catch (InvalidArgumentException) {
            return null;
        }
    }

    private function parseBoundary(string $value): ?Carbon
    {
        try {
            return Carbon::parse($value);
        } catch (\Exception) {
            return null;
        }
    }

    /**
     * @return array<string, mixed>
     */
    #[Computed]
    public function articleStats(): array
    {
        $range = $this->range();

        if (! $range instanceof DateRange) {
            return [
                'articles_fetched' => 0,
                'articles_published' => 0,
                'published_percentage' => 0.0,
            ];
        }

        return app(DashboardStatsService::class)->getStats($range);
    }

    #[Computed]
    public function articlesTrend(): SeriesResult
    {
        $range = $this->range();

        return $range instanceof DateRange
            ? app(ArticlesTrend::class)->for($range)
            : new SeriesResult([], []);
    }

    #[Computed]
    public function approvalRate(): SeriesResult
    {
        $range = $this->range();

        return $range instanceof DateRange
            ? app(ApprovalRate::class)->for($range)
            : new SeriesResult([], []);
    }

    #[Computed]
    public function publishSuccessRate(): SeriesResult
    {
        $range = $this->range();

        return $range instanceof DateRange
            ? app(PublishSuccessRate::class)->for($range)
            : new SeriesResult([], []);
    }

    #[Computed]
    public function articlesPerFeed(): BreakdownResult
    {
        return $this->breakdown(ArticlesPerFeed::class);
    }

    #[Computed]
    public function publicationsPerChannel(): BreakdownResult
    {
        return $this->breakdown(PublicationsPerChannel::class);
    }

    /**
     * @param  class-string<BreakdownStat>  $stat
     */
    private function breakdown(string $stat): BreakdownResult
    {
        $range = $this->range();

        return $range instanceof DateRange
            ? app($stat)->for($range)
            : new BreakdownResult([]);
    }

    /**
     * Deliberately range-independent: "recent" means latest, not latest within the filter.
     *
     * @return Collection<int, ActivityLog>
     */
    #[Computed]
    public function recentActivity(): Collection
    {
        return ActivityLog::query()
            ->withSubjectDetails()
            ->latestFirst()
            ->limit(self::RECENT_ACTIVITY_LIMIT)
            ->get();
    }

    /**
     * @return array<string, int>
     */
    #[Computed]
    public function systemStats(): array
    {
        return app(DashboardStatsService::class)->getSystemStats();
    }

    public function updated(string $property): void
    {
        if (in_array($property, ['from', 'to'], true)) {
            $this->validateRange();
            $this->refreshRangeDependentIslands();
        }
    }

    private function validateRange(): void
    {
        $this->resetErrorBag(['from', 'to']);

        $from = $this->parseBoundary($this->from);
        $to = $this->parseBoundary($this->to);

        if (! $from instanceof Carbon) {
            $this->addError('from', 'The start date is not a valid date.');
        }

        if (! $to instanceof Carbon) {
            $this->addError('to', 'The end date is not a valid date.');
        }

        if ($from instanceof Carbon && $to instanceof Carbon && $to->lessThan($from)) {
            $this->addError('to', 'The end date must not be earlier than the start date.');
        }

        $this->rangeIsValid = $this->range() instanceof DateRange;
    }

    public function render(): View
    {
        return view('livewire.dashboard')->layout('layouts.app');
    }
}
