<?php

declare(strict_types=1);

namespace App\Services\Sitemaps;

use App\Services\Sitemaps\Builders\GoogleNewsSitemapBuilder;

final class SitemapBuildService
{
    public function __construct(
        private readonly SitemapCacheService $cache,
        private readonly SitemapIndexService $index,
        private readonly SitemapRegistry $registry,
        private readonly SitemapShardService $shards,
        private readonly SitemapXmlRenderer $renderer,
    ) {
    }

    /**
     * @return array{content: string, source: string, type: string, url_count: int, shard_count: int, name: string}
     */
    public function buildIndex(bool $force = false, bool $persist = true, ?array $families = null): array
    {
        $built = $this->cache->remember(
            SitemapCacheService::INDEX_DOCUMENT,
            fn (): string => $this->renderer->renderIndex($this->index->items($families)),
            $force,
            $persist,
        );

        return $built + [
            'type' => SitemapTarget::TYPE_INDEX,
            'url_count' => count($this->index->items($families)),
            'shard_count' => 0,
            'name' => SitemapCacheService::INDEX_DOCUMENT,
        ];
    }

    /**
     * @return array{content: string, source: string, type: string, url_count: int, shard_count: int, name: string}|null
     */
    public function buildNamed(string $name, bool $force = false, bool $persist = true): ?array
    {
        $target = $this->shards->resolve($this->registry, $name);

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

        $built = $this->cache->remember(
            $target->documentName,
            fn (): string => $this->renderTarget($target),
            $force,
            $persist,
        );

        return $built + [
            'type' => $target->type,
            'url_count' => $this->urlCount($target),
            'shard_count' => $target->totalShards,
            'name' => $target->documentName,
        ];
    }

    /**
     * @return list<string>
     */
    public function documentNamesForFamily(string $family, bool $includeCompatibilityIndex = true): array
    {
        $builder = $this->registry->get($family);

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

        $names = $this->shards->canonicalDocumentNamesForBuilder($builder);

        if ($includeCompatibilityIndex && $builder instanceof ShardableSitemapBuilder && $this->shards->shardCount($builder) > 1) {
            array_unshift($names, $builder->name());
            foreach (range(1, $this->shards->shardCount($builder)) as $shard) {
                array_unshift($names, $builder->name() . '-' . $shard);
            }
        }

        return array_values(array_unique($names));
    }

    /**
     * @return list<string>
     */
    public function canonicalDocumentNamesForFamily(string $family): array
    {
        $builder = $this->registry->get($family);

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

        return $this->shards->canonicalDocumentNamesForBuilder($builder);
    }

    /**
     * @return list<string>
     */
    public function enabledFamilies(): array
    {
        return array_values(array_filter(
            (array) config('sitemaps.enabled', []),
            fn (mixed $name): bool => is_string($name) && $this->registry->get($name) !== null,
        ));
    }

    private function renderTarget(SitemapTarget $target): string
    {
        if ($target->type === SitemapTarget::TYPE_INDEX) {
            return $this->renderer->renderIndex($this->index->itemsForBuilder($target->builder));
        }

        if ($target->builder instanceof GoogleNewsSitemapBuilder || $target->type === SitemapTarget::TYPE_GOOGLE_NEWS) {
            return $this->renderer->renderGoogleNewsUrlset($target->builder->items());
        }

        if ($target->builder instanceof ShardableSitemapBuilder && $target->shardNumber !== null) {
            return $this->renderer->renderUrlset($target->builder->itemsForShard($target->shardNumber));
        }

        return $this->renderer->renderUrlset($target->builder->items());
    }

    private function urlCount(SitemapTarget $target): int
    {
        if ($target->type === SitemapTarget::TYPE_INDEX) {
            return count($this->index->itemsForBuilder($target->builder));
        }

        if ($target->builder instanceof ShardableSitemapBuilder && $target->shardNumber !== null) {
            return count($target->builder->itemsForShard($target->shardNumber));
        }

        return count($target->builder->items());
    }
}