<?php

declare(strict_types=1);

namespace App\Services\NovaCards;

use App\Models\NovaCard;
use App\Models\NovaCardCreatorPreset;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

class NovaCardCreatorPresetService
{
    /** Maximum presets per user across all types. */
    public const MAX_TOTAL = 30;

    /** Maximum presets per type per user. */
    public const MAX_PER_TYPE = 8;

    public function listForUser(User $user, ?string $presetType = null): Collection
    {
        return NovaCardCreatorPreset::query()
            ->where('user_id', $user->id)
            ->when($presetType !== null, fn ($q) => $q->where('preset_type', $presetType))
            ->orderByDesc('is_default')
            ->orderBy('name')
            ->get();
    }

    public function create(User $user, array $data): NovaCardCreatorPreset
    {
        $type = (string) Arr::get($data, 'preset_type', NovaCardCreatorPreset::TYPE_STYLE);

        // Enforce per-type limit.
        $typeCount = NovaCardCreatorPreset::query()
            ->where('user_id', $user->id)
            ->where('preset_type', $type)
            ->count();

        if ($typeCount >= self::MAX_PER_TYPE) {
            abort(422, 'Maximum number of ' . $type . ' presets reached (' . self::MAX_PER_TYPE . ').');
        }

        // Enforce total limit.
        $totalCount = NovaCardCreatorPreset::query()->where('user_id', $user->id)->count();
        if ($totalCount >= self::MAX_TOTAL) {
            abort(422, 'Maximum total presets reached (' . self::MAX_TOTAL . ').');
        }

        $preset = NovaCardCreatorPreset::query()->create([
            'user_id' => $user->id,
            'name' => (string) Arr::get($data, 'name', 'My preset'),
            'preset_type' => $type,
            'config_json' => $this->sanitizeConfig((array) Arr::get($data, 'config_json', []), $type),
            'is_default' => false,
        ]);

        if ((bool) Arr::get($data, 'is_default', false)) {
            $this->setDefault($user, $preset);
        }

        return $preset->refresh();
    }

    public function update(User $user, NovaCardCreatorPreset $preset, array $data): NovaCardCreatorPreset
    {
        $this->authorizeOwner($user, $preset);

        $preset->fill([
            'name' => (string) Arr::get($data, 'name', $preset->name),
            'config_json' => $this->sanitizeConfig(
                (array) Arr::get($data, 'config_json', $preset->config_json ?? []),
                $preset->preset_type,
            ),
        ]);
        $preset->save();

        if (array_key_exists('is_default', $data) && (bool) $data['is_default']) {
            $this->setDefault($user, $preset);
        }

        return $preset->refresh();
    }

    public function delete(User $user, NovaCardCreatorPreset $preset): void
    {
        $this->authorizeOwner($user, $preset);
        $preset->delete();
    }

    public function setDefault(User $user, NovaCardCreatorPreset|int $preset): void
    {
        if (is_int($preset)) {
            $preset = NovaCardCreatorPreset::query()->findOrFail($preset);
        }

        $this->authorizeOwner($user, $preset);

        // Clear existing defaults of the same type before setting new one.
        NovaCardCreatorPreset::query()
            ->where('user_id', $user->id)
            ->where('preset_type', $preset->preset_type)
            ->where('id', '!=', $preset->id)
            ->update(['is_default' => false]);

        $preset->update(['is_default' => true]);
    }

    /**
     * Capture a preset from a published or saved card project JSON.
     * Extracts only the fields relevant to the given preset type.
     */
    public function captureFromCard(User $user, NovaCard $card, string $presetName, string $presetType): NovaCardCreatorPreset
    {
        $project = is_array($card->project_json) ? $card->project_json : [];
        $config = $this->extractFromProject($project, $presetType);

        return $this->create($user, [
            'name' => $presetName,
            'preset_type' => $presetType,
            'config_json' => $config,
        ]);
    }

    /**
     * Apply a preset config on top of the given project patch array.
     * Returns the merged patch to be passed into the draft autosave flow.
     */
    public function applyToProjectPatch(NovaCardCreatorPreset $preset, array|NovaCard $currentProject): array
    {
        if ($currentProject instanceof NovaCard) {
            $currentProject = is_array($currentProject->project_json) ? $currentProject->project_json : [];
        }

        $config = $preset->config_json ?? [];

        return match ($preset->preset_type) {
            NovaCardCreatorPreset::TYPE_TYPOGRAPHY => [
                'typography' => array_merge(
                    (array) Arr::get($currentProject, 'typography', []),
                    (array) (Arr::get($config, 'typography', $config)),
                ),
            ],
            NovaCardCreatorPreset::TYPE_LAYOUT => [
                'layout' => array_merge(
                    (array) Arr::get($currentProject, 'layout', []),
                    (array) (Arr::get($config, 'layout', $config)),
                ),
            ],
            NovaCardCreatorPreset::TYPE_BACKGROUND => [
                'background' => array_merge(
                    (array) Arr::get($currentProject, 'background', []),
                    (array) (Arr::get($config, 'background', $config)),
                ),
            ],
            NovaCardCreatorPreset::TYPE_STYLE => array_filter([
                'typography' => isset($config['typography'])
                    ? array_merge((array) Arr::get($currentProject, 'typography', []), (array) $config['typography'])
                    : null,
                'background' => isset($config['background'])
                    ? array_merge((array) Arr::get($currentProject, 'background', []), (array) $config['background'])
                    : null,
                'effects' => isset($config['effects'])
                    ? array_merge((array) Arr::get($currentProject, 'effects', []), (array) $config['effects'])
                    : null,
            ]),
            NovaCardCreatorPreset::TYPE_STARTER => $config,
            default => [],
        };
    }

    private function extractFromProject(array $project, string $presetType): array
    {
        return match ($presetType) {
            NovaCardCreatorPreset::TYPE_TYPOGRAPHY => array_intersect_key(
                (array) Arr::get($project, 'typography', []),
                array_flip(['font_preset', 'text_color', 'accent_color', 'quote_size', 'author_size', 'letter_spacing', 'line_height', 'shadow_preset', 'quote_mark_preset', 'text_panel_style', 'text_glow', 'text_stroke']),
            ),
            NovaCardCreatorPreset::TYPE_LAYOUT => array_intersect_key(
                (array) Arr::get($project, 'layout', []),
                array_flip(['layout', 'position', 'alignment', 'padding', 'max_width']),
            ),
            NovaCardCreatorPreset::TYPE_BACKGROUND => array_intersect_key(
                (array) Arr::get($project, 'background', []),
                array_flip(['type', 'gradient_preset', 'gradient_colors', 'solid_color', 'overlay_style', 'blur_level', 'opacity', 'brightness', 'contrast', 'texture_overlay', 'gradient_direction']),
            ),
            NovaCardCreatorPreset::TYPE_STYLE => [
                'typography' => array_intersect_key(
                    (array) Arr::get($project, 'typography', []),
                    array_flip(['font_preset', 'text_color', 'accent_color', 'shadow_preset', 'quote_mark_preset', 'text_panel_style']),
                ),
                'background' => array_intersect_key(
                    (array) Arr::get($project, 'background', []),
                    array_flip(['gradient_preset', 'gradient_colors', 'overlay_style']),
                ),
                'effects' => (array) Arr::get($project, 'effects', []),
            ],
            NovaCardCreatorPreset::TYPE_STARTER => array_intersect_key($project, array_flip([
                'template', 'layout', 'typography', 'background', 'decorations', 'effects', 'frame',
            ])),
            default => [],
        };
    }

    private function sanitizeConfig(array $config, string $type): array
    {
        // Strip deeply nested unknowns and limit total size.
        $encoded = json_encode($config);
        if ($encoded === false || mb_strlen($encoded) > 32_768) {
            abort(422, 'Preset config is too large.');
        }

        return $config;
    }

    private function authorizeOwner(User $user, NovaCardCreatorPreset $preset): void
    {
        if ((int) $preset->user_id !== (int) $user->id) {
            abort(403);
        }
    }

    public function toArray(NovaCardCreatorPreset $preset): array
    {
        return [
            'id' => (int) $preset->id,
            'name' => (string) $preset->name,
            'preset_type' => (string) $preset->preset_type,
            'config_json' => $preset->config_json ?? [],
            'is_default' => (bool) $preset->is_default,
            'created_at' => optional($preset->created_at)?->toISOString(),
        ];
    }
}
