<?php

declare(strict_types=1);

namespace App\Services\Sitemaps;

final class SitemapShardService
{
    public function rootEntryName(SitemapBuilder $builder): string
    {
        $shardCount = $this->shardCount($builder);

        if ($builder instanceof ShardableSitemapBuilder && ($shardCount > 1 || $this->forceFamilyIndexes())) {
            return $this->familyIndexName($builder->name());
        }

        return $builder->name();
    }

    public function shardCount(SitemapBuilder $builder): int
    {
        if (! $builder instanceof ShardableSitemapBuilder || ! $this->enabledFor($builder->name())) {
            return 1;
        }

        $totalItems = $builder->totalItems();
        if ($totalItems <= 0) {
            return 0;
        }

        return max(1, (int) ceil($totalItems / max(1, $builder->shardSize())));
    }

    /**
     * @return list<string>
     */
    public function indexNamesForBuilder(SitemapBuilder $builder): array
    {
        $shardCount = $this->shardCount($builder);

        if ($builder instanceof ShardableSitemapBuilder && $shardCount > 1) {
            return array_map(fn (int $shard): string => $this->canonicalShardName($builder->name(), $shard), range(1, $shardCount));
        }

        return [$builder->name()];
    }

    /**
     * @return list<string>
     */
    public function canonicalDocumentNamesForBuilder(SitemapBuilder $builder): array
    {
        $names = $this->indexNamesForBuilder($builder);

        if ($builder instanceof ShardableSitemapBuilder && ($this->shardCount($builder) > 1 || $this->forceFamilyIndexes())) {
            array_unshift($names, $this->familyIndexName($builder->name()));
        }

        return array_values(array_unique($names));
    }

    public function familyIndexName(string $baseName): string
    {
        return $baseName . '-index';
    }

    public function canonicalShardName(string $baseName, int $shard): string
    {
        return sprintf('%s-%s', $baseName, str_pad((string) $shard, $this->padLength(), '0', STR_PAD_LEFT));
    }

    public function resolve(SitemapRegistry $registry, string $name): ?SitemapTarget
    {
        $builder = $registry->get($name);

        if ($builder !== null) {
            $shardCount = $this->shardCount($builder);

            if ($builder instanceof ShardableSitemapBuilder && ($shardCount > 1 || $this->forceFamilyIndexes())) {
                return new SitemapTarget($name, $this->familyIndexName($builder->name()), $builder->name(), SitemapTarget::TYPE_INDEX, $builder, null, $shardCount);
            }

            return new SitemapTarget($name, $builder->name(), $builder->name(), $this->targetType($builder), $builder);
        }

        if (preg_match('/^(.+)-index$/', $name, $indexMatches)) {
            $baseName = (string) $indexMatches[1];
            $builder = $registry->get($baseName);

            if (! $builder instanceof ShardableSitemapBuilder) {
                return null;
            }

            $shardCount = $this->shardCount($builder);

            if ($shardCount < 1) {
                return null;
            }

            return new SitemapTarget($name, $this->familyIndexName($baseName), $baseName, SitemapTarget::TYPE_INDEX, $builder, null, $shardCount);
        }

        if (! preg_match('/^(.+)-([0-9]{1,})$/', $name, $matches)) {
            return null;
        }

        $baseName = (string) $matches[1];
        $shardNumber = (int) $matches[2];
        $builder = $registry->get($baseName);

        if (! $builder instanceof ShardableSitemapBuilder) {
            return null;
        }

        $shardCount = $this->shardCount($builder);

        if ($shardCount < 2 || $shardNumber > $shardCount) {
            return null;
        }

        return new SitemapTarget($name, $this->canonicalShardName($baseName, $shardNumber), $baseName, SitemapTarget::TYPE_URLSET, $builder, $shardNumber, $shardCount);
    }

    private function forceFamilyIndexes(): bool
    {
        return (bool) config('sitemaps.shards.force_family_indexes', false);
    }

    private function padLength(): int
    {
        return max(1, (int) config('sitemaps.shards.zero_pad_length', 4));
    }

    private function targetType(SitemapBuilder $builder): string
    {
        return $builder->name() === (string) config('sitemaps.news.google_variant_name', 'news-google')
            ? SitemapTarget::TYPE_GOOGLE_NEWS
            : SitemapTarget::TYPE_URLSET;
    }

    private function enabledFor(string $family): bool
    {
        if (! (bool) config('sitemaps.shards.enabled', true)) {
            return false;
        }

        return (int) data_get(config('sitemaps.shards', []), $family . '.size', 0) > 0;
    }
}