<?php

declare(strict_types=1);

namespace App\Services\Studio;

use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Collection;

final class CreatorStudioCalendarService
{
    public function __construct(
        private readonly CreatorStudioContentService $content,
        private readonly CreatorStudioScheduledService $scheduled,
    ) {
    }

    public function build(User $user, array $filters = []): array
    {
        $view = $this->normalizeView((string) ($filters['view'] ?? 'month'));
        $module = $this->normalizeModule((string) ($filters['module'] ?? 'all'));
        $status = $this->normalizeStatus((string) ($filters['status'] ?? 'scheduled'));
        $query = trim((string) ($filters['q'] ?? ''));
        $focusDate = $this->normalizeFocusDate((string) ($filters['focus_date'] ?? ''));

        $scheduledItems = $this->scheduledItems($user, $module, $query);
        $unscheduledItems = $this->unscheduledItems($user, $module, $query);

        return [
            'filters' => [
                'view' => $view,
                'module' => $module,
                'status' => $status,
                'q' => $query,
                'focus_date' => $focusDate->toDateString(),
            ],
            'view_options' => [
                ['value' => 'month', 'label' => 'Month'],
                ['value' => 'week', 'label' => 'Week'],
                ['value' => 'agenda', 'label' => 'Agenda'],
            ],
            'status_options' => [
                ['value' => 'scheduled', 'label' => 'Scheduled only'],
                ['value' => 'unscheduled', 'label' => 'Unscheduled queue'],
                ['value' => 'all', 'label' => 'Everything'],
            ],
            'module_options' => array_merge([
                ['value' => 'all', 'label' => 'All content'],
            ], collect($this->content->moduleSummaries($user))->map(fn (array $summary): array => [
                'value' => $summary['key'],
                'label' => $summary['label'],
            ])->all()),
            'summary' => $this->summary($scheduledItems, $unscheduledItems),
            'month' => $this->monthGrid($scheduledItems, $focusDate),
            'week' => $this->weekGrid($scheduledItems, $focusDate),
            'agenda' => $this->agenda($scheduledItems),
            'scheduled_items' => $status === 'unscheduled' ? [] : $scheduledItems->take(18)->values()->all(),
            'unscheduled_items' => $status === 'scheduled' ? [] : $unscheduledItems->take(12)->values()->all(),
            'gaps' => $this->gaps($scheduledItems, $focusDate),
        ];
    }

    private function scheduledItems(User $user, string $module, string $query): Collection
    {
        $items = $module === 'all'
            ? collect($this->content->providers())->flatMap(fn ($provider) => $provider->scheduledItems($user, 320))
            : ($this->content->provider($module)?->scheduledItems($user, 320) ?? collect());

        if ($query !== '') {
            $needle = mb_strtolower($query);
            $items = $items->filter(fn (array $item): bool => str_contains(mb_strtolower((string) ($item['title'] ?? '')), $needle));
        }

        return $items
            ->sortBy(fn (array $item): int => strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? '')) ?: PHP_INT_MAX)
            ->values();
    }

    private function unscheduledItems(User $user, string $module, string $query): Collection
    {
        $items = $module === 'all'
            ? collect($this->content->providers())->flatMap(fn ($provider) => $provider->items($user, 'all', 240))
            : ($this->content->provider($module)?->items($user, 'all', 240) ?? collect());

        return $items
            ->filter(function (array $item) use ($query): bool {
                if (filled($item['scheduled_at'] ?? null)) {
                    return false;
                }

                if (in_array((string) ($item['status'] ?? ''), ['archived', 'hidden', 'rejected'], true)) {
                    return false;
                }

                if ($query === '') {
                    return true;
                }

                return str_contains(mb_strtolower((string) ($item['title'] ?? '')), mb_strtolower($query));
            })
            ->sortByDesc(fn (array $item): int => strtotime((string) ($item['updated_at'] ?? $item['created_at'] ?? '')) ?: 0)
            ->values();
    }

    private function summary(Collection $scheduledItems, Collection $unscheduledItems): array
    {
        $days = $scheduledItems
            ->groupBy(fn (array $item): string => date('Y-m-d', strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? now()->toIso8601String()))))
            ->map(fn (Collection $items): int => $items->count());

        return [
            'scheduled_total' => $scheduledItems->count(),
            'unscheduled_total' => $unscheduledItems->count(),
            'overloaded_days' => $days->filter(fn (int $count): bool => $count >= 3)->count(),
            'next_publish_at' => $scheduledItems->first()['scheduled_at'] ?? null,
        ];
    }

    private function monthGrid(Collection $scheduledItems, Carbon $focusDate): array
    {
        $start = $focusDate->copy()->startOfMonth()->startOfWeek();
        $end = $focusDate->copy()->endOfMonth()->endOfWeek();
        $days = [];

        for ($date = $start->copy(); $date->lte($end); $date->addDay()) {
            $key = $date->toDateString();
            $items = $scheduledItems->filter(fn (array $item): bool => str_starts_with((string) ($item['scheduled_at'] ?? ''), $key))->values();
            $days[] = [
                'date' => $key,
                'day' => $date->day,
                'is_current_month' => $date->month === $focusDate->month,
                'count' => $items->count(),
                'items' => $items->take(3)->all(),
            ];
        }

        return [
            'label' => $focusDate->format('F Y'),
            'days' => $days,
        ];
    }

    private function weekGrid(Collection $scheduledItems, Carbon $focusDate): array
    {
        $start = $focusDate->copy()->startOfWeek();

        return [
            'label' => $start->format('M j') . ' - ' . $start->copy()->endOfWeek()->format('M j'),
            'days' => collect(range(0, 6))->map(function (int $offset) use ($start, $scheduledItems): array {
                $date = $start->copy()->addDays($offset);
                $key = $date->toDateString();
                $items = $scheduledItems->filter(fn (array $item): bool => str_starts_with((string) ($item['scheduled_at'] ?? ''), $key))->values();

                return [
                    'date' => $key,
                    'label' => $date->format('D j'),
                    'items' => $items->all(),
                ];
            })->all(),
        ];
    }

    private function agenda(Collection $scheduledItems): array
    {
        return $scheduledItems
            ->groupBy(fn (array $item): string => date('Y-m-d', strtotime((string) ($item['scheduled_at'] ?? $item['published_at'] ?? now()->toIso8601String()))))
            ->map(fn (Collection $items, string $date): array => [
                'date' => $date,
                'label' => Carbon::parse($date)->format('M j'),
                'count' => $items->count(),
                'items' => $items->values()->all(),
            ])
            ->values()
            ->all();
    }

    private function gaps(Collection $scheduledItems, Carbon $focusDate): array
    {
        return collect(range(0, 13))
            ->map(function (int $offset) use ($focusDate, $scheduledItems): ?array {
                $date = $focusDate->copy()->startOfDay()->addDays($offset);
                $key = $date->toDateString();
                $count = $scheduledItems->filter(fn (array $item): bool => str_starts_with((string) ($item['scheduled_at'] ?? ''), $key))->count();

                if ($count > 0) {
                    return null;
                }

                return [
                    'date' => $key,
                    'label' => $date->format('D, M j'),
                ];
            })
            ->filter()
            ->take(6)
            ->values()
            ->all();
    }

    private function normalizeView(string $view): string
    {
        return in_array($view, ['month', 'week', 'agenda'], true) ? $view : 'month';
    }

    private function normalizeStatus(string $status): string
    {
        return in_array($status, ['scheduled', 'unscheduled', 'all'], true) ? $status : 'scheduled';
    }

    private function normalizeModule(string $module): string
    {
        return in_array($module, ['all', 'artworks', 'cards', 'collections', 'stories'], true) ? $module : 'all';
    }

    private function normalizeFocusDate(string $value): Carbon
    {
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1) {
            return Carbon::parse($value);
        }

        return now();
    }
}