<?php

declare(strict_types=1);

namespace App\Services\NovaCards;

use App\Models\NovaCard;
use App\Models\NovaCardAssetPack;
use App\Models\NovaCardCategory;
use App\Models\NovaCardCollection;
use App\Models\NovaCardChallenge;
use App\Models\NovaCardCreatorPreset;
use App\Models\NovaCardReaction;
use App\Models\NovaCardTemplate;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;

class NovaCardPresenter
{
    public function __construct(
        private readonly NovaCardProjectNormalizer $normalizer,
        private readonly NovaCardPublishModerationService $moderation,
    ) {
    }

    public function options(): array
    {
        $assetPacks = collect((array) config('nova_cards.asset_packs', []))
            ->map(fn (array $pack): array => ['id' => null, ...$pack])
            ->values();
        $templatePacks = collect((array) config('nova_cards.template_packs', []))
            ->map(fn (array $pack): array => ['id' => null, ...$pack])
            ->values();

        $databasePacks = NovaCardAssetPack::query()
            ->where('active', true)
            ->orderBy('order_num')
            ->orderBy('name')
            ->get()
            ->map(fn (NovaCardAssetPack $pack): array => [
                'id' => (int) $pack->id,
                'slug' => (string) $pack->slug,
                'name' => (string) $pack->name,
                'description' => $pack->description,
                'type' => (string) $pack->type,
                'preview_image' => $pack->preview_image,
                'manifest_json' => $pack->manifest_json,
                'official' => (bool) $pack->official,
                'active' => (bool) $pack->active,
            ]);

        return [
            'formats' => collect((array) config('nova_cards.formats', []))
                ->map(fn (array $format, string $key): array => [
                    'key' => $key,
                    'label' => (string) ($format['label'] ?? ucfirst($key)),
                    'width' => (int) ($format['width'] ?? 1080),
                    'height' => (int) ($format['height'] ?? 1080),
                ])
                ->values()
                ->all(),
            'font_presets' => collect((array) config('nova_cards.font_presets', []))
                ->map(fn (array $font, string $key): array => ['key' => $key, ...$font])
                ->values()
                ->all(),
            'gradient_presets' => collect((array) config('nova_cards.gradient_presets', []))
                ->map(fn (array $gradient, string $key): array => ['key' => $key, ...$gradient])
                ->values()
                ->all(),
            'decor_presets' => array_values((array) config('nova_cards.decor_presets', [])),
            'background_modes' => array_values((array) config('nova_cards.background_modes', [])),
            'layout_presets' => array_values((array) config('nova_cards.layout_presets', [])),
            'alignment_presets' => array_values((array) config('nova_cards.alignment_presets', [])),
            'position_presets' => array_values((array) config('nova_cards.position_presets', [])),
            'padding_presets' => array_values((array) config('nova_cards.padding_presets', [])),
            'max_width_presets' => array_values((array) config('nova_cards.max_width_presets', [])),
            'line_height_presets' => array_values((array) config('nova_cards.line_height_presets', [])),
            'shadow_presets' => array_values((array) config('nova_cards.shadow_presets', [])),
            'focal_positions' => array_values((array) config('nova_cards.focal_positions', [])),
            'categories' => NovaCardCategory::query()
                ->where('active', true)
                ->orderBy('order_num')
                ->orderBy('name')
                ->get()
                ->map(fn (NovaCardCategory $category): array => [
                    'id' => (int) $category->id,
                    'slug' => (string) $category->slug,
                    'name' => (string) $category->name,
                    'description' => $category->description,
                ])
                ->values()
                ->all(),
            'templates' => NovaCardTemplate::query()
                ->where('active', true)
                ->orderBy('order_num')
                ->orderBy('name')
                ->get()
                ->map(fn (NovaCardTemplate $template): array => [
                    'id' => (int) $template->id,
                    'slug' => (string) $template->slug,
                    'name' => (string) $template->name,
                    'description' => $template->description,
                    'preview_image' => $template->preview_image,
                    'supported_formats' => $template->supported_formats,
                    'config_json' => $template->config_json,
                    'official' => (bool) $template->official,
                ])
                ->values()
                ->all(),
            'asset_packs' => $assetPacks
                ->concat($databasePacks->where('type', NovaCardAssetPack::TYPE_ASSET)->values())
                ->values()
                ->all(),
            'template_packs' => $templatePacks
                ->concat($databasePacks->where('type', NovaCardAssetPack::TYPE_TEMPLATE)->values())
                ->values()
                ->all(),
            'challenge_feed' => NovaCardChallenge::query()
                ->whereIn('status', [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED])
                ->orderByDesc('featured')
                ->orderBy('starts_at')
                ->limit(8)
                ->get()
                ->map(fn (NovaCardChallenge $challenge): array => [
                    'id' => (int) $challenge->id,
                    'slug' => (string) $challenge->slug,
                    'title' => (string) $challenge->title,
                    'description' => $challenge->description,
                    'prompt' => $challenge->prompt,
                    'status' => (string) $challenge->status,
                    'official' => (bool) $challenge->official,
                    'featured' => (bool) $challenge->featured,
                    'entries_count' => (int) $challenge->entries_count,
                    'starts_at' => optional($challenge->starts_at)?->toISOString(),
                    'ends_at' => optional($challenge->ends_at)?->toISOString(),
                ])
                ->values()
                ->all(),
            'validation' => (array) config('nova_cards.validation', []),
            // v3 additions
            'quote_mark_presets' => array_values((array) config('nova_cards.quote_mark_presets', [])),
            'text_panel_styles' => array_values((array) config('nova_cards.text_panel_styles', [])),
            'frame_presets' => array_values((array) config('nova_cards.frame_presets', [])),
            'color_grade_presets' => array_values((array) config('nova_cards.color_grade_presets', [])),
            'effect_presets' => array_values((array) config('nova_cards.effect_presets', [])),
            'style_families' => array_values((array) config('nova_cards.style_families', [])),
            'export_formats' => collect((array) config('nova_cards.export_formats', []))
                ->map(fn ($fmt, $key) => array_merge(['key' => $key], (array) $fmt))
                ->values()
                ->all(),
        ];
    }

    public function optionsWithPresets(array $options, User $user): array
    {
        $presets = NovaCardCreatorPreset::query()
            ->where('user_id', $user->id)
            ->orderByDesc('is_default')
            ->orderBy('name')
            ->get()
            ->groupBy('preset_type')
            ->map(fn ($group) => $group->map(fn ($p) => [
                'id' => (int) $p->id,
                'name' => (string) $p->name,
                'preset_type' => (string) $p->preset_type,
                'config_json' => $p->config_json ?? [],
                'is_default' => (bool) $p->is_default,
            ])->values()->all())
            ->all();

        return array_merge($options, ['creator_presets' => $presets]);
    }

    public function card(NovaCard $card, bool $withProject = false, ?User $viewer = null): array
    {
        $card->loadMissing(['user.profile', 'category', 'template', 'backgroundImage', 'tags', 'originalCard', 'rootCard']);
        $project = $this->normalizer->normalizeForCard($card);
        $viewerCollections = $viewer
            ? NovaCardCollection::query()
                ->where('user_id', $viewer->id)
                ->whereHas('cards', fn ($query) => $query->where('nova_cards.id', $card->id))
                ->pluck('id')
                ->map(fn ($id): int => (int) $id)
                ->values()
                ->all()
            : [];
        $viewerReactions = $viewer
            ? NovaCardReaction::query()
                ->where('user_id', $viewer->id)
                ->where('card_id', $card->id)
                ->pluck('type')
                ->all()
            : [];
        $moderationReasons = $this->moderation->storedReasons($card);
        $moderationOverride = $this->moderation->latestOverride($card);

        return [
            'id' => (int) $card->id,
            'uuid' => (string) $card->uuid,
            'title' => (string) $card->title,
            'slug' => (string) $card->slug,
            'quote_text' => (string) $card->quote_text,
            'quote_author' => $card->quote_author,
            'quote_source' => $card->quote_source,
            'description' => $card->description,
            'format' => (string) $card->format,
            'visibility' => (string) $card->visibility,
            'status' => (string) $card->status,
            'moderation_status' => (string) $card->moderation_status,
            'moderation_reasons' => $moderationReasons,
            'moderation_reason_labels' => $this->moderation->labelsFor($moderationReasons),
            'moderation_source' => $this->moderation->storedSource($card),
            'moderation_override' => $moderationOverride,
            'moderation_override_history' => $this->moderation->overrideHistory($card),
            'featured' => (bool) $card->featured,
            'allow_download' => (bool) $card->allow_download,
            'background_type' => (string) $card->background_type,
            'background_image_id' => $card->background_image_id ? (int) $card->background_image_id : null,
            'template_id' => $card->template_id ? (int) $card->template_id : null,
            'category_id' => $card->category_id ? (int) $card->category_id : null,
            'preview_url' => $card->previewUrl(),
            'og_preview_url' => $card->ogPreviewUrl(),
            'public_url' => $card->publicUrl(),
            'published_at' => optional($card->published_at)?->toISOString(),
            'scheduled_for' => optional($card->scheduled_for)?->toISOString(),
            'scheduling_timezone' => $card->scheduling_timezone,
            'render_version' => (int) $card->render_version,
            'schema_version' => (int) $card->schema_version,
            'views_count' => (int) $card->views_count,
            'shares_count' => (int) $card->shares_count,
            'downloads_count' => (int) $card->downloads_count,
            'likes_count' => (int) $card->likes_count,
            'favorites_count' => (int) $card->favorites_count,
            'saves_count' => (int) $card->saves_count,
            'remixes_count' => (int) $card->remixes_count,
            'comments_count' => (int) $card->comments_count,
            'challenge_entries_count' => (int) $card->challenge_entries_count,
            'allow_remix' => (bool) $card->allow_remix,
            'allow_background_reuse' => (bool) $card->allow_background_reuse,
            'allow_export' => (bool) $card->allow_export,
            'style_family' => $card->style_family,
            'palette_family' => $card->palette_family,
            'editor_mode_last_used' => $card->editor_mode_last_used,
            'featured_score' => $card->featured_score !== null ? (float) $card->featured_score : null,
            'last_engaged_at' => optional($card->last_engaged_at)?->toISOString(),
            'last_ranked_at' => optional($card->last_ranked_at)?->toISOString(),
            'creator' => [
                'id' => (int) $card->user->id,
                'username' => (string) $card->user->username,
                'name' => $card->user->name,
            ],
            'category' => $card->category ? [
                'id' => (int) $card->category->id,
                'slug' => (string) $card->category->slug,
                'name' => (string) $card->category->name,
            ] : null,
            'template' => $card->template ? [
                'id' => (int) $card->template->id,
                'slug' => (string) $card->template->slug,
                'name' => (string) $card->template->name,
                'description' => $card->template->description,
                'config_json' => $card->template->config_json,
                'supported_formats' => $card->template->supported_formats,
            ] : null,
            'background_image' => $card->backgroundImage ? [
                'id' => (int) $card->backgroundImage->id,
                'processed_url' => $card->backgroundImage->processedUrl(),
                'width' => (int) $card->backgroundImage->width,
                'height' => (int) $card->backgroundImage->height,
            ] : null,
            'tags' => $card->tags->map(fn ($tag): array => [
                'id' => (int) $tag->id,
                'slug' => (string) $tag->slug,
                'name' => (string) $tag->name,
            ])->values()->all(),
            'lineage' => [
                'original_card_id' => $card->original_card_id ? (int) $card->original_card_id : null,
                'root_card_id' => $card->root_card_id ? (int) $card->root_card_id : null,
                'original_card' => $card->originalCard ? [
                    'id' => (int) $card->originalCard->id,
                    'title' => (string) $card->originalCard->title,
                    'slug' => (string) $card->originalCard->slug,
                ] : null,
                'root_card' => $card->rootCard ? [
                    'id' => (int) $card->rootCard->id,
                    'title' => (string) $card->rootCard->title,
                    'slug' => (string) $card->rootCard->slug,
                ] : null,
            ],
            'version_count' => $card->relationLoaded('versions') ? $card->versions->count() : $card->versions()->count(),
            'can_edit' => $viewer ? $card->isOwnedBy($viewer) : false,
            'viewer_state' => [
                'liked' => in_array(NovaCardReaction::TYPE_LIKE, $viewerReactions, true),
                'favorited' => in_array(NovaCardReaction::TYPE_FAVORITE, $viewerReactions, true),
                'saved_collection_ids' => $viewerCollections,
            ],
            'project_json' => $withProject ? $project : null,
        ];
    }

    public function cards(iterable $cards, bool $withProject = false, ?User $viewer = null): array
    {
        return collect($cards)
            ->map(fn (NovaCard $card): array => $this->card($card, $withProject, $viewer))
            ->values()
            ->all();
    }

    public function collection(NovaCardCollection $collection, ?User $viewer = null, bool $withCards = false): array
    {
        $collection->loadMissing(['user', 'items.card.user.profile', 'items.card.category', 'items.card.template', 'items.card.backgroundImage', 'items.card.tags']);

        $items = $collection->items
            ->filter(fn ($item): bool => $item->card !== null && $item->card->canBeViewedBy($viewer))
            ->sortBy('sort_order')
            ->values();

        return [
            'id' => (int) $collection->id,
            'slug' => (string) $collection->slug,
            'name' => (string) $collection->name,
            'description' => $collection->description,
            'visibility' => (string) $collection->visibility,
            'official' => (bool) $collection->official,
            'featured' => (bool) $collection->featured,
            'cards_count' => (int) $collection->cards_count,
            'public_url' => $collection->publicUrl(),
            'owner' => [
                'id' => (int) $collection->user->id,
                'username' => (string) $collection->user->username,
                'name' => $collection->user->name,
            ],
            'cover_card' => $items->isNotEmpty() ? $this->card($items->first()->card, false, $viewer) : null,
            'items' => $withCards ? $items->map(fn ($item): array => [
                'id' => (int) $item->id,
                'note' => $item->note,
                'sort_order' => (int) $item->sort_order,
                'card' => $this->card($item->card, false, $viewer),
            ])->values()->all() : [],
        ];
    }

    public function paginator(LengthAwarePaginator $paginator, bool $withProject = false, ?User $viewer = null): array
    {
        return [
            'data' => $this->cards($paginator->items(), $withProject, $viewer),
            'meta' => [
                'current_page' => $paginator->currentPage(),
                'last_page' => $paginator->lastPage(),
                'per_page' => $paginator->perPage(),
                'total' => $paginator->total(),
                'from' => $paginator->firstItem(),
                'to' => $paginator->lastItem(),
            ],
        ];
    }
}
