user()?->hasStaffAccess(); } protected function prepareForValidation(): void { $blocks = collect($this->input('blocks', [])) ->filter(static fn ($block): bool => is_array($block)) ->map(function (array $block): array { $payload = Arr::wrap($block['payload'] ?? []); $criteria = collect($payload['criteria'] ?? []) ->map(static fn ($criterion) => trim((string) $criterion)) ->filter(static fn (string $criterion): bool => $criterion !== '') ->values() ->all(); $results = collect($block['comparison_results'] ?? []) ->filter(static fn ($result): bool => is_array($result)) ->map(function (array $result): array { return [ 'id' => filled($result['id'] ?? null) ? (int) $result['id'] : null, 'provider' => $result['provider'] ?? null, 'model_name' => $result['model_name'] ?? null, 'image_path' => $result['image_path'] ?? null, 'thumb_path' => $result['thumb_path'] ?? null, 'settings' => $result['settings'] ?? null, 'strengths' => $result['strengths'] ?? null, 'weaknesses' => $result['weaknesses'] ?? null, 'best_for' => $result['best_for'] ?? null, 'score' => filled($result['score'] ?? null) ? (int) $result['score'] : null, 'sort_order' => filled($result['sort_order'] ?? null) ? (int) $result['sort_order'] : 0, 'active' => filter_var($result['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true, ]; }) ->values() ->all(); return [ 'id' => filled($block['id'] ?? null) ? (int) $block['id'] : null, 'type' => (string) ($block['type'] ?? 'ai_comparison'), 'title' => $block['title'] ?? null, 'payload' => [ 'title' => $payload['title'] ?? null, 'intro' => $payload['intro'] ?? null, 'prompt' => $payload['prompt'] ?? null, 'negative_prompt' => $payload['negative_prompt'] ?? null, 'aspect_ratio' => $payload['aspect_ratio'] ?? null, 'criteria' => $criteria, ], 'sort_order' => filled($block['sort_order'] ?? null) ? (int) $block['sort_order'] : 0, 'active' => filter_var($block['active'] ?? true, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE) ?? true, 'comparison_results' => $results, ]; }) ->values() ->all(); $this->merge([ 'reading_minutes' => $this->filled('reading_minutes') ? (int) $this->input('reading_minutes') : 5, 'featured' => $this->boolean('featured'), 'active' => $this->boolean('active', true), 'blocks' => $blocks, ]); } public function rules(): array { $lessonId = $this->route('academyLesson')?->id; return [ 'category_id' => ['nullable', 'integer', 'exists:academy_categories,id'], 'title' => ['required', 'string', 'max:180'], 'slug' => ['required', 'string', 'max:180', Rule::unique('academy_lessons', 'slug')->ignore($lessonId)], 'excerpt' => ['nullable', 'string'], 'content' => ['nullable', 'string'], 'difficulty' => ['required', 'string', Rule::in((array) config('academy.difficulty_levels', []))], 'access_level' => ['required', 'string', Rule::in(['free', 'creator', 'pro'])], 'lesson_type' => ['required', 'string', 'max:80'], 'cover_image' => ['nullable', 'string', 'max:2048'], 'video_url' => ['nullable', 'string', 'max:2048'], 'reading_minutes' => ['required', 'integer', 'min:1', 'max:999'], 'featured' => ['required', 'boolean'], 'active' => ['required', 'boolean'], 'published_at' => ['nullable', 'date'], 'seo_title' => ['nullable', 'string', 'max:180'], 'seo_description' => ['nullable', 'string', 'max:255'], 'blocks' => ['nullable', 'array'], 'blocks.*.id' => ['nullable', 'integer', 'exists:academy_lesson_blocks,id'], 'blocks.*.type' => ['required', 'string', Rule::in(['ai_comparison'])], 'blocks.*.title' => ['nullable', 'string', 'max:255'], 'blocks.*.payload' => ['nullable', 'array'], 'blocks.*.payload.title' => ['nullable', 'string', 'max:255'], 'blocks.*.payload.intro' => ['nullable', 'string'], 'blocks.*.payload.prompt' => ['nullable', 'string'], 'blocks.*.payload.negative_prompt' => ['nullable', 'string'], 'blocks.*.payload.aspect_ratio' => ['nullable', 'string', 'max:20'], 'blocks.*.payload.criteria' => ['nullable', 'array'], 'blocks.*.payload.criteria.*' => ['nullable', 'string', 'max:100'], 'blocks.*.sort_order' => ['required', 'integer', 'min:0'], 'blocks.*.active' => ['required', 'boolean'], 'blocks.*.comparison_results' => ['nullable', 'array'], 'blocks.*.comparison_results.*.id' => ['nullable', 'integer', 'exists:academy_ai_comparison_results,id'], 'blocks.*.comparison_results.*.provider' => ['nullable', 'string', 'max:100'], 'blocks.*.comparison_results.*.model_name' => ['nullable', 'string', 'max:150'], 'blocks.*.comparison_results.*.image_path' => ['required', 'string', 'max:500'], 'blocks.*.comparison_results.*.thumb_path' => ['nullable', 'string', 'max:500'], 'blocks.*.comparison_results.*.settings' => ['nullable', 'string'], 'blocks.*.comparison_results.*.strengths' => ['nullable', 'string'], 'blocks.*.comparison_results.*.weaknesses' => ['nullable', 'string'], 'blocks.*.comparison_results.*.best_for' => ['nullable', 'string'], 'blocks.*.comparison_results.*.score' => ['nullable', 'integer', 'min:1', 'max:10'], 'blocks.*.comparison_results.*.sort_order' => ['required', 'integer', 'min:0'], 'blocks.*.comparison_results.*.active' => ['required', 'boolean'], ]; } }