<?php

declare(strict_types=1);

namespace App\Services\NovaCards;

use App\Events\NovaCards\NovaCardAutosaved;
use App\Events\NovaCards\NovaCardCreated;
use App\Events\NovaCards\NovaCardTemplateSelected;
use App\Models\NovaCard;
use App\Models\NovaCardCategory;
use App\Models\NovaCardTemplate;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class NovaCardDraftService
{
    public function __construct(
        private readonly NovaCardTagService $tagService,
        private readonly NovaCardProjectNormalizer $normalizer,
        private readonly NovaCardVersionService $versions,
    ) {
    }

    public function createDraft(User $user, array $attributes = []): NovaCard
    {
        $template = $this->resolveTemplate(Arr::get($attributes, 'template_id'));
        $category = $this->resolveCategory(Arr::get($attributes, 'category_id'));
        $title = trim((string) Arr::get($attributes, 'title', 'Untitled card'));
        $quote = trim((string) Arr::get($attributes, 'quote_text', 'Your next quote starts here.'));
        $project = $this->normalizer->upgradeToV2(null, $template, $attributes);
        $topLevel = $this->normalizer->syncTopLevelAttributes($project);
        $originalCardId = Arr::get($attributes, 'original_card_id');
        $rootCardId = Arr::get($attributes, 'root_card_id', $originalCardId);

        $card = NovaCard::query()->create([
            'user_id' => $user->id,
            'category_id' => $category?->id,
            'template_id' => $template?->id,
            'title' => Str::limit($topLevel['title'] ?: $title, (int) config('nova_cards.validation.title_max', 120), ''),
            'slug' => $this->generateUniqueSlug($title),
            'quote_text' => Str::limit($topLevel['quote_text'] ?: $quote, (int) config('nova_cards.validation.quote_max', 420), ''),
            'quote_author' => $topLevel['quote_author'],
            'quote_source' => $topLevel['quote_source'],
            'description' => Arr::get($attributes, 'description'),
            'format' => $this->resolveFormat((string) Arr::get($attributes, 'format', NovaCard::FORMAT_SQUARE)),
            'project_json' => $project,
            'schema_version' => (int) $topLevel['schema_version'],
            'visibility' => NovaCard::VISIBILITY_PRIVATE,
            'status' => NovaCard::STATUS_DRAFT,
            'moderation_status' => NovaCard::MOD_PENDING,
            'background_type' => $topLevel['background_type'],
            'background_image_id' => $topLevel['background_image_id'],
            'allow_download' => true,
            'allow_remix' => (bool) Arr::get($attributes, 'allow_remix', true),
            'allow_background_reuse' => (bool) Arr::get($attributes, 'allow_background_reuse', false),
            'allow_export' => (bool) Arr::get($attributes, 'allow_export', true),
            'style_family' => Arr::get($attributes, 'style_family'),
            'palette_family' => Arr::get($attributes, 'palette_family'),
            'original_card_id' => $originalCardId,
            'root_card_id' => $rootCardId,
            'scheduling_timezone' => Arr::get($attributes, 'scheduling_timezone'),
        ]);

        $this->tagService->syncTags($card, Arr::wrap(Arr::get($attributes, 'tags', [])));
        $this->versions->snapshot($card->fresh(['template']), $user, $originalCardId ? 'Remix draft created' : 'Initial draft', true);

        event(new NovaCardCreated($card->fresh()->load(['category', 'template', 'tags'])));

        if ($card->template_id !== null) {
            event(new NovaCardTemplateSelected($card->fresh()->load(['category', 'template', 'tags']), null, (int) $card->template_id));
        }

        return $card->load(['category', 'template', 'tags']);
    }

    public function autosave(NovaCard $card, array $payload): NovaCard
    {
        $currentProject = $this->normalizer->upgradeToV2(is_array($card->project_json) ? $card->project_json : [], $card->template, [], $card);
        $template = $this->resolveTemplateId($payload, $card)
            ? NovaCardTemplate::query()->find($this->resolveTemplateId($payload, $card))
            : $card->template;
        $projectPatch = is_array(Arr::get($payload, 'project_json')) ? Arr::get($payload, 'project_json') : [];
        $normalizedProject = $this->normalizer->upgradeToV2(
            array_replace_recursive($currentProject, $projectPatch),
            $template,
            array_merge($payload, [
                'title' => Arr::get($payload, 'title', $card->title),
                'quote_text' => Arr::get($payload, 'quote_text', $card->quote_text),
                'quote_author' => Arr::get($payload, 'quote_author', $card->quote_author),
                'quote_source' => Arr::get($payload, 'quote_source', $card->quote_source),
                'background_type' => Arr::get($payload, 'background_type', $card->background_type),
                'background_image_id' => Arr::get($payload, 'background_image_id', $card->background_image_id),
                'original_card_id' => $card->original_card_id,
                'root_card_id' => $card->root_card_id,
            ]),
            $card,
        );
        $topLevel = $this->normalizer->syncTopLevelAttributes($normalizedProject);

        if (array_key_exists('title', $payload)) {
            $topLevel['title'] = trim((string) $payload['title']);
        }

        if (array_key_exists('quote_text', $payload)) {
            $topLevel['quote_text'] = trim((string) $payload['quote_text']);
        }

        if (array_key_exists('quote_author', $payload)) {
            $topLevel['quote_author'] = (($value = trim((string) $payload['quote_author'])) !== '') ? $value : null;
        }

        if (array_key_exists('quote_source', $payload)) {
            $topLevel['quote_source'] = (($value = trim((string) $payload['quote_source'])) !== '') ? $value : null;
        }

        $previousTemplateId = $card->template_id ? (int) $card->template_id : null;

        $card->fill([
            'title' => Str::limit($topLevel['title'], (int) config('nova_cards.validation.title_max', 120), ''),
            'quote_text' => Str::limit($topLevel['quote_text'], (int) config('nova_cards.validation.quote_max', 420), ''),
            'quote_author' => $topLevel['quote_author'],
            'quote_source' => $topLevel['quote_source'],
            'description' => Arr::get($payload, 'description', $card->description),
            'format' => $this->resolveFormat((string) Arr::get($payload, 'format', $card->format)),
            'project_json' => $normalizedProject,
            'schema_version' => (int) $topLevel['schema_version'],
            'background_type' => $topLevel['background_type'],
            'background_image_id' => $topLevel['background_image_id'],
            'template_id' => $this->resolveTemplateId($payload, $card),
            'category_id' => $this->resolveCategoryId($payload, $card),
            'visibility' => Arr::get($payload, 'visibility', $card->visibility),
            'allow_download' => (bool) Arr::get($payload, 'allow_download', $card->allow_download),
            'allow_remix' => (bool) Arr::get($payload, 'allow_remix', $card->allow_remix),
            'allow_background_reuse' => (bool) Arr::get($payload, 'allow_background_reuse', $card->allow_background_reuse ?? false),
            'allow_export' => (bool) Arr::get($payload, 'allow_export', $card->allow_export ?? true),
            'style_family' => Arr::get($payload, 'style_family', $card->style_family),
            'palette_family' => Arr::get($payload, 'palette_family', $card->palette_family),
            'editor_mode_last_used' => Arr::get($payload, 'editor_mode_last_used', $card->editor_mode_last_used),
            'scheduling_timezone' => Arr::get($payload, 'scheduling_timezone', $card->scheduling_timezone),
        ]);

        if ($card->isDirty('title')) {
            $card->slug = $this->generateUniqueSlug($card->title, $card->id);
        }

        $card->save();
        $changes = $card->getChanges();

        if (array_key_exists('tags', $payload)) {
            $this->tagService->syncTags($card, Arr::wrap(Arr::get($payload, 'tags', [])));
            $changes['tags'] = true;
        }

        if ($changes !== []) {
            $fresh = $card->fresh()->load(['category', 'template', 'tags']);
            $this->versions->snapshot($fresh->loadMissing('template'), $fresh->user, Arr::get($payload, 'version_label'));
            event(new NovaCardAutosaved($fresh, array_keys($changes)));

            $currentTemplateId = $fresh->template_id ? (int) $fresh->template_id : null;
            if ($currentTemplateId !== $previousTemplateId && $currentTemplateId !== null) {
                event(new NovaCardTemplateSelected($fresh, $previousTemplateId, $currentTemplateId));
            }
        }

        return $card->refresh()->load(['category', 'template', 'tags']);
    }

    public function createRemix(User $user, NovaCard $source, array $attributes = []): NovaCard
    {
        $baseProject = $this->normalizer->normalizeForCard($source->loadMissing('template'));
        $payload = array_merge([
            'title' => 'Remix of ' . $source->title,
            'quote_text' => $source->quote_text,
            'quote_author' => $source->quote_author,
            'quote_source' => $source->quote_source,
            'description' => $source->description,
            'format' => $source->format,
            'background_type' => $source->background_type,
            'background_image_id' => $source->background_image_id,
            'template_id' => $source->template_id,
            'category_id' => $source->category_id,
            'tags' => $source->tags->pluck('name')->all(),
            'project_json' => $baseProject,
            'original_card_id' => $source->id,
            'root_card_id' => $source->root_card_id ?: $source->id,
        ], $attributes);

        return $this->createDraft($user, $payload);
    }

    public function createDuplicate(User $user, NovaCard $source, array $attributes = []): NovaCard
    {
        abort_unless($source->isOwnedBy($user), 403);

        $baseProject = $this->normalizer->normalizeForCard($source->loadMissing('template'));
        $payload = array_merge([
            'title' => 'Copy of ' . $source->title,
            'quote_text' => $source->quote_text,
            'quote_author' => $source->quote_author,
            'quote_source' => $source->quote_source,
            'description' => $source->description,
            'format' => $source->format,
            'background_type' => $source->background_type,
            'background_image_id' => $source->background_image_id,
            'template_id' => $source->template_id,
            'category_id' => $source->category_id,
            'tags' => $source->tags->pluck('name')->all(),
            'project_json' => $baseProject,
            'allow_remix' => $source->allow_remix,
        ], $attributes);

        return $this->createDraft($user, $payload);
    }

    private function buildProjectPayload(?NovaCardTemplate $template, array $attributes): array
    {
        return $this->normalizer->normalize(null, $template, $attributes);
    }

    private function generateUniqueSlug(string $title, ?int $ignoreId = null): string
    {
        $base = Str::slug($title);
        if ($base === '') {
            $base = 'nova-card';
        }

        $slug = $base;
        $suffix = 2;

        while (NovaCard::query()
            ->when($ignoreId !== null, fn ($query) => $query->where('id', '!=', $ignoreId))
            ->where('slug', $slug)
            ->exists()) {
            $slug = $base . '-' . $suffix;
            $suffix++;
        }

        return $slug;
    }

    private function resolveTemplate(mixed $templateId): ?NovaCardTemplate
    {
        if ($templateId) {
            return NovaCardTemplate::query()->find($templateId);
        }

        return NovaCardTemplate::query()->where('active', true)->orderBy('order_num')->first();
    }

    private function resolveCategory(mixed $categoryId): ?NovaCardCategory
    {
        if ($categoryId) {
            return NovaCardCategory::query()->find($categoryId);
        }

        return NovaCardCategory::query()->where('active', true)->orderBy('order_num')->first();
    }

    private function resolveFormat(string $format): string
    {
        $formats = array_keys((array) config('nova_cards.formats', []));

        return in_array($format, $formats, true) ? $format : NovaCard::FORMAT_SQUARE;
    }

    private function resolveTemplateId(array $payload, NovaCard $card): ?int
    {
        if (! array_key_exists('template_id', $payload)) {
            return $card->template_id;
        }

        return $this->resolveTemplate(Arr::get($payload, 'template_id'))?->id;
    }

    private function resolveCategoryId(array $payload, NovaCard $card): ?int
    {
        if (! array_key_exists('category_id', $payload)) {
            return $card->category_id;
        }

        return $this->resolveCategory(Arr::get($payload, 'category_id'))?->id;
    }
}
