<?php

declare(strict_types=1);

namespace App\Support\Seo;

use App\Models\Artwork;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

final class SeoFactory
{
    /**
     * @param  array<string, mixed>  $meta
     */
    public function homepage(array $meta): SeoData
    {
        $description = trim((string) ($meta['description'] ?? config('seo.default_description')));

        return SeoDataBuilder::make()
            ->title((string) ($meta['title'] ?? config('seo.default_title')))
            ->description($description)
            ->keywords($meta['keywords'] ?? null)
            ->canonical((string) ($meta['canonical'] ?? url('/')))
            ->og(type: 'website', image: $meta['og_image'] ?? null)
            ->addJsonLd([
                '@context' => 'https://schema.org',
                '@type' => 'WebSite',
                'name' => (string) config('seo.site_name', 'Skinbase'),
                'url' => url('/'),
                'description' => $description,
                'potentialAction' => [
                    '@type' => 'SearchAction',
                    'target' => url('/search') . '?q={search_term_string}',
                    'query-input' => 'required name=search_term_string',
                ],
            ])
            ->build();
    }

    /**
     * @param  array<string, array<string, mixed>|null>  $thumbs
     */
    public function artwork(Artwork $artwork, array $thumbs, string $canonical): SeoData
    {
        $authorName = html_entity_decode((string) ($artwork->user?->name ?: $artwork->user?->username ?: 'Artist'), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $title = html_entity_decode((string) ($artwork->title ?: 'Artwork'), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $description = trim(strip_tags(html_entity_decode((string) ($artwork->description ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8')));
        $description = Str::limit($description !== '' ? $description : $title, 160, '…');
        $image = $thumbs['xl']['url'] ?? $thumbs['lg']['url'] ?? $thumbs['md']['url'] ?? null;
        $keywords = $artwork->tags->pluck('name')->filter()->unique()->values()->all();

        return SeoDataBuilder::make()
            ->title(sprintf('%s by %s — %s', $title, $authorName, config('seo.site_name', 'Skinbase')))
            ->description($description)
            ->keywords($keywords)
            ->canonical($canonical)
            ->og(type: 'article', image: $image)
            ->addJsonLd(array_filter([
                '@context' => 'https://schema.org',
                '@type' => 'ImageObject',
                'name' => $title,
                'description' => $description,
                'url' => $canonical,
                'contentUrl' => $image,
                'thumbnailUrl' => $thumbs['md']['url'] ?? $image,
                'encodingFormat' => 'image/webp',
                'width' => $thumbs['xl']['width'] ?? $thumbs['lg']['width'] ?? null,
                'height' => $thumbs['xl']['height'] ?? $thumbs['lg']['height'] ?? null,
                'author' => ['@type' => 'Person', 'name' => $authorName],
                'datePublished' => optional($artwork->published_at)->toAtomString(),
                'license' => $artwork->license_url,
                'keywords' => $keywords !== [] ? $keywords : null,
            ], fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []))
            ->addJsonLd(array_filter([
                '@context' => 'https://schema.org',
                '@type' => 'CreativeWork',
                'name' => $title,
                'description' => $description,
                'url' => $canonical,
                'author' => ['@type' => 'Person', 'name' => $authorName],
                'datePublished' => optional($artwork->published_at)->toAtomString(),
                'license' => $artwork->license_url,
                'keywords' => $keywords !== [] ? $keywords : null,
                'image' => $image,
            ], fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []))
            ->build();
    }

    public function profilePage(string $title, string $canonical, string $description, ?string $image = null, iterable $breadcrumbs = []): SeoData
    {
        $profileName = trim(str_replace([' Gallery on Skinbase', ' on Skinbase'], '', $title));

        return SeoDataBuilder::make()
            ->title($title)
            ->description($description)
            ->canonical($canonical)
            ->breadcrumbs($breadcrumbs)
            ->og(type: 'profile', image: $image)
            ->addJsonLd([
                '@context' => 'https://schema.org',
                '@type' => 'ProfilePage',
                'name' => $title,
                'description' => $description,
                'url' => $canonical,
                'mainEntity' => array_filter([
                    '@type' => 'Person',
                    'name' => $profileName,
                    'url' => $canonical,
                    'image' => $image,
                ], fn (mixed $value): bool => $value !== null && $value !== ''),
            ])
            ->build();
    }

    public function collectionListing(string $title, string $description, string $canonical, ?string $image = null, bool $indexable = true): SeoData
    {
        return SeoDataBuilder::make()
            ->title($title)
            ->description($description)
            ->canonical($canonical)
            ->indexable($indexable)
            ->og(type: 'website', image: $image)
            ->build();
    }

    public function collectionPage(string $title, string $description, string $canonical, ?string $image = null, bool $indexable = true): SeoData
    {
        return SeoDataBuilder::make()
            ->title($title)
            ->description($description)
            ->canonical($canonical)
            ->indexable($indexable)
            ->og(type: 'website', image: $image)
            ->build();
    }

    public function leaderboardPage(string $title, string $description, string $canonical): SeoData
    {
        return SeoDataBuilder::make()
            ->title($title)
            ->description($description)
            ->canonical($canonical)
            ->og(type: 'website')
            ->build();
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>
     */
    public function fromViewData(array $data): array
    {
        if (($data['seo'] ?? null) instanceof SeoData) {
            return $data['seo']->toArray();
        }

        if (is_array($data['seo'] ?? null) && ($data['seo'] ?? []) !== []) {
            return SeoDataBuilder::fromArray($data['seo'])->build()->toArray();
        }

        $attributes = [
            'title' => $data['page_title'] ?? data_get($data, 'meta.title') ?? data_get($data, 'metaTitle'),
            'description' => $data['page_meta_description'] ?? data_get($data, 'meta.description') ?? data_get($data, 'metaDescription'),
            'keywords' => $data['page_meta_keywords'] ?? data_get($data, 'meta.keywords'),
            'canonical' => $data['page_canonical'] ?? data_get($data, 'meta.canonical') ?? url()->current(),
            'robots' => $data['page_robots'] ?? data_get($data, 'meta.robots'),
            'prev' => $data['page_rel_prev'] ?? null,
            'next' => $data['page_rel_next'] ?? null,
            'breadcrumbs' => $data['breadcrumbs'] ?? [],
            'structured_data' => Arr::wrap($data['structured_data'] ?? []),
            'faq_schema' => Arr::wrap($data['faq_schema'] ?? []),
            'og_type' => $data['seo_og_type'] ?? data_get($data, 'meta.og_type'),
            'og_title' => $data['og_title'] ?? data_get($data, 'meta.og_title'),
            'og_description' => $data['og_description'] ?? data_get($data, 'meta.og_description'),
            'og_url' => $data['og_url'] ?? data_get($data, 'meta.og_url'),
            'og_image' => $data['og_image']
                ?? $data['ogImage']
                ?? data_get($data, 'meta.og_image')
                ?? data_get($data, 'meta.ogImage')
                ?? data_get($data, 'props.hero.thumb_lg')
                ?? data_get($data, 'props.hero.thumb')
                ?? null,
            'og_image_alt' => $data['og_image_alt'] ?? null,
        ];

        $builder = SeoDataBuilder::fromArray($attributes);

        if (($data['gallery_type'] ?? null) !== null) {
            $builder->addJsonLd($this->gallerySchema($data));
        }

        return $builder->build()->toArray();
    }

    /**
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>|null
     */
    private function gallerySchema(array $data): ?array
    {
        $artworks = $data['artworks'] ?? null;
        if (! $artworks instanceof AbstractPaginator && ! $artworks instanceof Collection && ! is_array($artworks)) {
            return null;
        }

        $items = $artworks instanceof AbstractPaginator
            ? collect($artworks->items())
            : collect($artworks);

        $itemListElement = $items
            ->take(12)
            ->values()
            ->map(function (mixed $artwork, int $index): ?array {
                $name = trim((string) (data_get($artwork, 'name') ?? data_get($artwork, 'title') ?? ''));
                $url = data_get($artwork, 'url');

                if (! filled($url)) {
                    $slug = data_get($artwork, 'slug');
                    $id = data_get($artwork, 'id');
                    if (filled($slug) && filled($id)) {
                        $url = route('art.show', ['id' => $id, 'slug' => $slug]);
                    }
                }

                if ($name === '' && ! filled($url)) {
                    return null;
                }

                return array_filter([
                    '@type' => 'ListItem',
                    'position' => $index + 1,
                    'name' => $name !== '' ? $name : null,
                    'url' => filled($url) ? url((string) $url) : null,
                ], fn (mixed $value): bool => $value !== null && $value !== '');
            })
            ->filter()
            ->values()
            ->all();

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

        $count = $artworks instanceof AbstractPaginator ? $artworks->total() : count($itemListElement);

        return [
            '@context' => 'https://schema.org',
            '@type' => 'CollectionPage',
            'name' => (string) ($data['page_title'] ?? $data['hero_title'] ?? config('seo.default_title', 'Skinbase')),
            'description' => (string) ($data['page_meta_description'] ?? $data['hero_description'] ?? config('seo.default_description')),
            'url' => (string) ($data['page_canonical'] ?? url()->current()),
            'mainEntity' => [
                '@type' => 'ItemList',
                'numberOfItems' => $count,
                'itemListElement' => $itemListElement,
            ],
        ];
    }
}