<?php

declare(strict_types=1);

namespace App\Services\Studio;

use App\Models\Artwork;
use App\Services\TagNormalizer;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

final class StudioAiSuggestionBuilder
{
    private const GENERIC_TAGS = [
        'image', 'picture', 'artwork', 'art', 'design', 'visual', 'graphic', 'photo of', 'image of',
    ];

    public function __construct(
        private readonly TagNormalizer $normalizer,
    ) {
    }

    /**
     * @param array<string, mixed> $analysis
     */
    public function detectMode(Artwork $artwork, array $analysis): string
    {
        $signals = Collection::make([
            $artwork->title,
            $artwork->description,
            $artwork->file_name,
            $analysis['blip_caption'] ?? null,
            ...Collection::make((array) ($analysis['clip_tags'] ?? []))->pluck('tag')->all(),
            ...Collection::make((array) ($analysis['yolo_objects'] ?? []))->pluck('tag')->all(),
        ])->filter()->implode(' ');

        return preg_match('/\b(screenshot|screen|ui|interface|menu|hud|dashboard|settings|launcher|app|game)\b/i', $signals) === 1
            ? 'screenshot'
            : 'artwork';
    }

    /**
     * @param array<string, mixed> $analysis
     * @return array<int, array{text: string, confidence: float}>
     */
    public function buildTitleSuggestions(Artwork $artwork, array $analysis, string $mode): array
    {
        $caption = $this->cleanCaption((string) ($analysis['blip_caption'] ?? ''));
        $topTerms = $this->topTerms($analysis, 4);
        $titleSeeds = Collection::make([
            $this->titleCase($caption),
            $this->titleCase($this->limitWords($caption, 6)),
            $mode === 'screenshot'
                ? $this->titleCase(trim(implode(' ', array_slice($topTerms, 0, 2)) . ' Screen'))
                : $this->titleCase(trim(implode(' ', array_slice($topTerms, 0, 3)))),
            $mode === 'screenshot'
                ? $this->titleCase(trim(implode(' ', array_slice($topTerms, 0, 2)) . ' Interface'))
                : $this->titleCase(trim(implode(' ', array_slice($topTerms, 0, 2)) . ' Study')),
            $mode === 'screenshot'
                ? $this->titleCase(trim(($topTerms[0] ?? 'Interface') . ' View'))
                : $this->titleCase(trim(($topTerms[0] ?? 'Artwork') . ' Composition')),
        ])
            ->filter(fn (?string $value): bool => is_string($value) && trim($value) !== '')
            ->map(fn (string $value): string => Str::limit(trim($value), 80, ''))
            ->unique()
            ->take(5)
            ->values();

        return $titleSeeds->map(fn (string $text, int $index): array => [
            'text' => $text,
            'confidence' => round(max(0.55, 0.92 - ($index * 0.07)), 2),
        ])->all();
    }

    /**
     * @param array<string, mixed> $analysis
     * @return array<int, array{variant: string, text: string, confidence: float}>
     */
    public function buildDescriptionSuggestions(Artwork $artwork, array $analysis, string $mode): array
    {
        $caption = $this->cleanCaption((string) ($analysis['blip_caption'] ?? ''));
        $terms = $this->topTerms($analysis, 5);
        $termSentence = $terms !== [] ? implode(', ', array_slice($terms, 0, 3)) : null;

        $short = $caption !== ''
            ? Str::ucfirst(Str::finish($caption, '.'))
            : ($mode === 'screenshot'
                ? 'A clear screenshot with interface-focused visual details.'
                : 'A visually focused artwork with clear subject and style cues.');

        $normal = $short;
        if ($termSentence) {
            $normal .= ' It highlights ' . $termSentence . ' without overclaiming details.';
        }

        $seo = $artwork->title !== ''
            ? $artwork->title . ' is presented with ' . ($termSentence ?: ($mode === 'screenshot' ? 'useful interface context' : 'strong visual detail')) . ' for discovery on Skinbase.'
            : $normal;

        return [
            ['variant' => 'short', 'text' => Str::limit($short, 180, ''), 'confidence' => 0.89],
            ['variant' => 'normal', 'text' => Str::limit($normal, 280, ''), 'confidence' => 0.85],
            ['variant' => 'seo', 'text' => Str::limit($seo, 220, ''), 'confidence' => 0.8],
        ];
    }

    /**
     * @param array<string, mixed> $analysis
     * @return array<int, array{tag: string, confidence: float|null}>
     */
    public function buildTagSuggestions(Artwork $artwork, array $analysis, string $mode): array
    {
        $rawTags = Collection::make()
            ->merge((array) ($analysis['clip_tags'] ?? []))
            ->merge((array) ($analysis['yolo_objects'] ?? []))
            ->map(function (mixed $item): array {
                if (is_string($item)) {
                    return ['tag' => $item, 'confidence' => null];
                }

                return [
                    'tag' => (string) ($item['tag'] ?? ''),
                    'confidence' => isset($item['confidence']) && is_numeric($item['confidence']) ? (float) $item['confidence'] : null,
                ];
            });

        foreach ($this->extractCaptionTags((string) ($analysis['blip_caption'] ?? '')) as $captionTag) {
            $rawTags->push(['tag' => $captionTag, 'confidence' => 0.62]);
        }

        if ($mode === 'screenshot') {
            foreach (['screenshot', 'ui'] as $fallbackTag) {
                $rawTags->push(['tag' => $fallbackTag, 'confidence' => 0.58]);
            }
        }

        $suggestions = $rawTags
            ->map(function (array $row): ?array {
                $tag = $this->normalizer->normalize((string) ($row['tag'] ?? ''));
                if ($tag === '' || in_array($tag, self::GENERIC_TAGS, true)) {
                    return null;
                }

                return [
                    'tag' => $tag,
                    'confidence' => isset($row['confidence']) && is_numeric($row['confidence']) ? round((float) $row['confidence'], 2) : null,
                ];
            })
            ->filter()
            ->unique('tag')
            ->sortByDesc(fn (array $row): float => (float) ($row['confidence'] ?? 0.0))
            ->take(15)
            ->values();

        return $suggestions->all();
    }

    /**
     * @param array<string, mixed> $analysis
     * @return array<int, string>
     */
    public function buildSignals(Artwork $artwork, array $analysis): array
    {
        return Collection::make([
            $artwork->title,
            $artwork->description,
            $artwork->file_name,
            $analysis['blip_caption'] ?? null,
            ...Collection::make((array) ($analysis['clip_tags'] ?? []))->pluck('tag')->all(),
            ...Collection::make((array) ($analysis['yolo_objects'] ?? []))->pluck('tag')->all(),
            ...$artwork->tags->pluck('slug')->all(),
        ])
            ->filter(fn (mixed $value): bool => is_string($value) && trim($value) !== '')
            ->values()
            ->all();
    }

    /**
     * @param array<string, mixed> $analysis
     * @return array<int, string>
     */
    private function topTerms(array $analysis, int $limit): array
    {
        return Collection::make()
            ->merge((array) ($analysis['clip_tags'] ?? []))
            ->merge((array) ($analysis['yolo_objects'] ?? []))
            ->map(fn (mixed $item): string => trim((string) (is_array($item) ? ($item['tag'] ?? '') : $item)))
            ->filter()
            ->flatMap(fn (string $term): array => preg_split('/\s+/', Str::of($term)->replace('-', ' ')->value()) ?: [])
            ->filter(fn (string $term): bool => strlen($term) >= 3)
            ->map(fn (string $term): string => Str::title($term))
            ->unique()
            ->take($limit)
            ->values()
            ->all();
    }

    /**
     * @return array<int, string>
     */
    private function extractCaptionTags(string $caption): array
    {
        $clean = Str::of($caption)
            ->lower()
            ->replaceMatches('/[^a-z0-9\s\-]+/', ' ')
            ->replace('-', ' ')
            ->squish()
            ->value();

        if ($clean === '') {
            return [];
        }

        $tokens = Collection::make(explode(' ', $clean))
            ->filter(fn (string $value): bool => strlen($value) >= 3)
            ->reject(fn (string $value): bool => in_array($value, ['with', 'from', 'into', 'over', 'under', 'image', 'picture', 'artwork'], true))
            ->values();

        $bigrams = [];
        for ($index = 0; $index < $tokens->count() - 1; $index++) {
            $bigrams[] = $tokens[$index] . ' ' . $tokens[$index + 1];
        }

        return $tokens->merge($bigrams)->unique()->take(10)->all();
    }

    private function cleanCaption(string $caption): string
    {
        return Str::of($caption)
            ->replaceMatches('/^(a|an|the)\s+/i', '')
            ->replaceMatches('/^(image|photo|screenshot) of\s+/i', '')
            ->squish()
            ->value();
    }

    private function titleCase(string $value): string
    {
        return Str::title(trim($value));
    }

    private function limitWords(string $value, int $maxWords): string
    {
        $words = preg_split('/\s+/', trim($value)) ?: [];

        return implode(' ', array_slice($words, 0, $maxWords));
    }
}