<?php

declare(strict_types=1);

namespace App\Http\Requests\Academy;

use JsonException;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpsertAcademyPromptTemplateRequest extends FormRequest
{
    public function authorize(): bool
    {
        return (bool) $this->user()?->hasStaffAccess();
    }

    protected function prepareForValidation(): void
    {
        $this->merge([
            'featured' => $this->boolean('featured'),
            'prompt_of_week' => $this->boolean('prompt_of_week'),
            'active' => $this->boolean('active', true),
            'new_category_name' => trim((string) $this->input('new_category_name', '')),
            'tags' => array_values(array_filter((array) $this->input('tags', []))),
            'documentation' => $this->normalizeDocumentation($this->input('documentation')),
            'placeholders' => $this->normalizePlaceholders($this->input('placeholders')),
            'helper_prompts' => $this->normalizeHelperPrompts($this->input('helper_prompts')),
            'prompt_variants' => $this->normalizePromptVariants($this->input('prompt_variants')),
            'tool_notes' => collect($this->input('tool_notes', []))
                ->filter(static fn ($note): bool => is_array($note) || is_string($note))
                ->map(function ($note): array|string {
                    if (is_string($note)) {
                        return $note;
                    }

                    return [
                        'display_type' => $note['display_type'] ?? null,
                        'provider' => $note['provider'] ?? null,
                        'model_name' => $note['model_name'] ?? null,
                        'notes' => $note['notes'] ?? null,
                        'strengths' => $note['strengths'] ?? null,
                        'weaknesses' => $note['weaknesses'] ?? null,
                        'best_for' => $note['best_for'] ?? null,
                        'image_path' => $note['image_path'] ?? null,
                        'thumb_path' => $note['thumb_path'] ?? null,
                        'settings' => $note['settings'] ?? null,
                        'score' => filled($note['score'] ?? null) ? (int) $note['score'] : null,
                        'active' => filter_var($note['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true,
                    ];
                })
                ->values()
                ->all(),
        ]);
    }

    public function rules(): array
    {
        $promptId = $this->route('academyPromptTemplate')?->id;

        return [
            'category_id' => ['nullable', 'integer', 'exists:academy_categories,id'],
            'new_category_name' => ['nullable', 'string', 'max:120'],
            'title' => ['required', 'string', 'max:180'],
            'slug' => ['required', 'string', 'max:180', Rule::unique('academy_prompt_templates', 'slug')->ignore($promptId)],
            'excerpt' => ['nullable', 'string'],
            'prompt' => ['required', 'string'],
            'negative_prompt' => ['nullable', 'string'],
            'usage_notes' => ['nullable', 'string'],
            'workflow_notes' => ['nullable', 'string'],
            'documentation' => ['nullable', 'array'],
            'documentation.summary' => ['nullable', 'string'],
            'documentation.best_for' => ['nullable', 'array'],
            'documentation.best_for.*' => ['nullable', 'string'],
            'documentation.how_to_use' => ['nullable', 'array'],
            'documentation.how_to_use.*' => ['nullable', 'string'],
            'documentation.required_inputs' => ['nullable', 'array'],
            'documentation.required_inputs.*' => ['nullable', 'string'],
            'documentation.workflow' => ['nullable', 'array'],
            'documentation.workflow.*' => ['nullable', 'string'],
            'documentation.tips' => ['nullable', 'array'],
            'documentation.tips.*' => ['nullable', 'string'],
            'documentation.common_mistakes' => ['nullable', 'array'],
            'documentation.common_mistakes.*' => ['nullable', 'string'],
            'documentation.data_accuracy_notes' => ['nullable', 'array'],
            'documentation.data_accuracy_notes.*' => ['nullable', 'string'],
            'documentation.display_notes' => ['nullable', 'string'],
            'placeholders' => ['nullable', 'array'],
            'placeholders.*.key' => ['nullable', 'string', 'max:120'],
            'placeholders.*.label' => ['nullable', 'string', 'max:180'],
            'placeholders.*.description' => ['nullable', 'string'],
            'placeholders.*.required' => ['nullable', 'boolean'],
            'placeholders.*.example' => ['nullable'],
            'placeholders.*.default' => ['nullable'],
            'placeholders.*.type' => ['nullable', 'string', 'max:120'],
            'helper_prompts' => ['nullable', 'array'],
            'helper_prompts.*.title' => ['required_with:helper_prompts', 'string', 'max:180'],
            'helper_prompts.*.type' => ['nullable', 'string', Rule::in(['data_collection', 'prompt_preparation', 'refinement', 'validation', 'variation', 'translation', 'seo', 'other'])],
            'helper_prompts.*.description' => ['nullable', 'string'],
            'helper_prompts.*.prompt' => ['required_with:helper_prompts', 'string'],
            'helper_prompts.*.expected_output' => ['nullable', 'string', Rule::in(['json', 'text', 'markdown', 'image_prompt'])],
            'helper_prompts.*.active' => ['nullable', 'boolean'],
            'prompt_variants' => ['nullable', 'array'],
            'prompt_variants.*.title' => ['required_with:prompt_variants', 'string', 'max:180'],
            'prompt_variants.*.slug' => ['nullable', 'string', 'max:180'],
            'prompt_variants.*.description' => ['nullable', 'string'],
            'prompt_variants.*.prompt' => ['required_with:prompt_variants', 'string'],
            'prompt_variants.*.negative_prompt' => ['nullable', 'string'],
            'prompt_variants.*.recommended' => ['nullable', 'boolean'],
            'prompt_variants.*.recommended_for' => ['nullable', 'array'],
            'prompt_variants.*.recommended_for.*' => ['nullable', 'string'],
            'prompt_variants.*.risk_notes' => ['nullable', 'array'],
            'prompt_variants.*.risk_notes.*' => ['nullable', 'string'],
            'prompt_variants.*.active' => ['nullable', 'boolean'],
            'difficulty' => ['required', 'string', Rule::in((array) config('academy.difficulty_levels', []))],
            'access_level' => ['required', 'string', Rule::in(['free', 'creator', 'pro'])],
            'aspect_ratio' => ['nullable', 'string', 'max:20'],
            'tags' => ['nullable', 'array'],
            'tags.*' => ['string', 'max:60'],
            'tool_notes' => ['nullable', 'array'],
                        'tool_notes.*.display_type' => ['nullable', 'string', 'max:50'],
                        'tool_notes.*.provider' => ['nullable', 'string', 'max:100'],
                        'tool_notes.*.model_name' => ['nullable', 'string', 'max:150'],
                        'tool_notes.*.notes' => ['nullable', 'string'],
                        'tool_notes.*.strengths' => ['nullable', 'string'],
                        'tool_notes.*.weaknesses' => ['nullable', 'string'],
                        'tool_notes.*.best_for' => ['nullable', 'string'],
                        'tool_notes.*.image_path' => ['nullable', 'string', 'max:500'],
                        'tool_notes.*.thumb_path' => ['nullable', 'string', 'max:500'],
                        'tool_notes.*.settings' => ['nullable', 'string'],
                        'tool_notes.*.score' => ['nullable', 'integer', 'min:1', 'max:10'],
                        'tool_notes.*.active' => ['nullable', 'boolean'],
            'preview_image' => ['nullable', 'string', 'max:2048'],
            'preview_image_file' => ['nullable', 'file', 'image', 'mimes:jpg,jpeg,png,webp', 'max:5120'],
            'featured' => ['required', 'boolean'],
            'prompt_of_week' => ['required', 'boolean'],
            'active' => ['required', 'boolean'],
            'published_at' => ['nullable', 'date'],
            'seo_title' => ['nullable', 'string', 'max:180'],
            'seo_description' => ['nullable', 'string', 'max:255'],
        ];
    }

    private function decodeStructuredInput(mixed $value): mixed
    {
        if (! is_string($value)) {
            return $value;
        }

        $trimmed = trim($value);

        if ($trimmed === '') {
            return null;
        }

        try {
            return json_decode($trimmed, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            return $value;
        }
    }

    private function normalizeDocumentation(mixed $value): mixed
    {
        $value = $this->decodeStructuredInput($value);

        if ($value === null) {
            return null;
        }

        if (! is_array($value)) {
            return $value;
        }

        $listFields = ['best_for', 'how_to_use', 'required_inputs', 'workflow', 'tips', 'common_mistakes', 'data_accuracy_notes'];
        $documentation = [
            'summary' => $this->normalizeOptionalString($value['summary'] ?? null),
            'display_notes' => $this->normalizeOptionalString($value['display_notes'] ?? null),
        ];

        foreach ($listFields as $field) {
            $documentation[$field] = $this->normalizeStringList($value[$field] ?? []);
        }

        $hasContent = $documentation['summary'] !== null
            || $documentation['display_notes'] !== null
            || collect($listFields)->contains(fn (string $field): bool => $documentation[$field] !== []);

        return $hasContent ? $documentation : null;
    }

    private function normalizePlaceholders(mixed $value): mixed
    {
        $value = $this->decodeStructuredInput($value);

        if ($value === null) {
            return [];
        }

        if (! is_array($value)) {
            return $value;
        }

        $value = $this->normalizeStructuredObjectList($value, ['key', 'label', 'description', 'required', 'example', 'default', 'type']);

        return collect($value)
            ->values()
            ->map(function ($placeholder): mixed {
                if (! is_array($placeholder)) {
                    return $placeholder;
                }

                return [
                    'key' => $this->normalizeOptionalString($placeholder['key'] ?? null),
                    'label' => $this->normalizeOptionalString($placeholder['label'] ?? null),
                    'description' => $this->normalizeOptionalString($placeholder['description'] ?? null),
                    'required' => filter_var($placeholder['required'] ?? false, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? false,
                    'example' => $this->normalizeJsonScalar($placeholder['example'] ?? null),
                    'default' => $this->normalizeJsonScalar($placeholder['default'] ?? null),
                    'type' => $this->normalizeOptionalString($placeholder['type'] ?? null),
                ];
            })
            ->filter(function ($placeholder): bool {
                if (! is_array($placeholder)) {
                    return true;
                }

                return collect([
                    $placeholder['key'] ?? null,
                    $placeholder['label'] ?? null,
                    $placeholder['description'] ?? null,
                    $placeholder['example'] ?? null,
                    $placeholder['default'] ?? null,
                    $placeholder['type'] ?? null,
                ])->contains(fn ($item): bool => $item !== null && $item !== '' && $item !== []);
            })
            ->values()
            ->all();
    }

    private function normalizeHelperPrompts(mixed $value): mixed
    {
        $value = $this->decodeStructuredInput($value);

        if ($value === null) {
            return [];
        }

        if (! is_array($value)) {
            return $value;
        }

        $value = $this->normalizeStructuredObjectList($value, ['title', 'type', 'description', 'prompt', 'expected_output', 'active']);

        return collect($value)
            ->values()
            ->map(function ($helperPrompt): mixed {
                if (! is_array($helperPrompt)) {
                    return $helperPrompt;
                }

                return [
                    'title' => $this->normalizeOptionalString($helperPrompt['title'] ?? null),
                    'type' => $this->normalizeOptionalString($helperPrompt['type'] ?? null) ?? 'other',
                    'description' => $this->normalizeOptionalString($helperPrompt['description'] ?? null),
                    'prompt' => $this->normalizeOptionalString($helperPrompt['prompt'] ?? null),
                    'expected_output' => $this->normalizeOptionalString($helperPrompt['expected_output'] ?? null) ?? 'text',
                    'active' => filter_var($helperPrompt['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true,
                ];
            })
            ->filter(function ($helperPrompt): bool {
                if (! is_array($helperPrompt)) {
                    return true;
                }

                return collect([
                    $helperPrompt['title'] ?? null,
                    $helperPrompt['description'] ?? null,
                    $helperPrompt['prompt'] ?? null,
                ])->contains(fn ($item): bool => $item !== null && $item !== '');
            })
            ->values()
            ->all();
    }

    private function normalizePromptVariants(mixed $value): mixed
    {
        $value = $this->decodeStructuredInput($value);

        if ($value === null) {
            return [];
        }

        if (! is_array($value)) {
            return $value;
        }

        $value = $this->normalizeStructuredObjectList($value, ['title', 'slug', 'description', 'prompt', 'negative_prompt', 'recommended', 'recommended_for', 'risk_notes', 'active']);

        return collect($value)
            ->values()
            ->map(function ($variant): mixed {
                if (! is_array($variant)) {
                    return $variant;
                }

                return [
                    'title' => $this->normalizeOptionalString($variant['title'] ?? null),
                    'slug' => $this->normalizeOptionalString($variant['slug'] ?? null),
                    'description' => $this->normalizeOptionalString($variant['description'] ?? null),
                    'prompt' => $this->normalizeOptionalString($variant['prompt'] ?? null),
                    'negative_prompt' => $this->normalizeOptionalString($variant['negative_prompt'] ?? null),
                    'recommended' => filter_var($variant['recommended'] ?? false, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? false,
                    'recommended_for' => $this->normalizeStringList($variant['recommended_for'] ?? []),
                    'risk_notes' => $this->normalizeStringList($variant['risk_notes'] ?? []),
                    'active' => filter_var($variant['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true,
                ];
            })
            ->filter(function ($variant): bool {
                if (! is_array($variant)) {
                    return true;
                }

                return collect([
                    $variant['title'] ?? null,
                    $variant['description'] ?? null,
                    $variant['prompt'] ?? null,
                    $variant['negative_prompt'] ?? null,
                ])->contains(fn ($item): bool => $item !== null && $item !== '');
            })
            ->values()
            ->all();
    }

    private function normalizeStringList(mixed $value): array
    {
        if (! is_array($value)) {
            $value = $value === null ? [] : [$value];
        }

        return collect($value)
            ->map(fn ($item): string => trim((string) $item))
            ->filter(static fn (string $item): bool => $item !== '')
            ->values()
            ->all();
    }

    private function normalizeOptionalString(mixed $value): ?string
    {
        if ($value === null) {
            return null;
        }

        $normalized = trim((string) $value);

        return $normalized !== '' ? $normalized : null;
    }

    private function normalizeJsonScalar(mixed $value): mixed
    {
        if (! is_string($value)) {
            return $value;
        }

        $trimmed = trim($value);

        return $trimmed !== '' ? $trimmed : null;
    }

    /**
     * @param  array<int|string, mixed>  $value
     * @param  array<int, string>  $expectedKeys
     * @return array<int|string, mixed>
     */
    private function normalizeStructuredObjectList(array $value, array $expectedKeys): array
    {
        if (array_is_list($value)) {
            return $value;
        }

        $keys = array_keys($value);
        $normalizedKeys = array_map(static fn ($key): string => (string) $key, $keys);

        if ($normalizedKeys === [] || array_intersect($normalizedKeys, $expectedKeys) === []) {
            return $value;
        }

        return [$value];
    }
}