<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Core\Relationships;

use Ekesto\CacheInvalidator\InvalidatorRuntime;

/**
 * Lite-safe relationship-graph reader.
 *
 * Builds the configured host -> relationships adjacency (and its inversion) from
 * plugin settings. This is pure settings/config reading: it applies NO filters,
 * performs NO host inference, and contains NO premium propagation logic. It is
 * shared by the Lite facade (configured/watched post types) and, when present,
 * the premium relationship provider (BFS traversal source).
 *
 * Graph model:
 * - Settings store host -> relationships adjacency.
 * - Runtime traversal uses inverted edges related -> hosts.
 */
final class RelationshipGraph
{
    /** @var array<string,mixed>|null */
    private static ?array $graphCache = null;

    /**
     * All configured post types (keys under settings post_types).
     *
     * @return string[]
     */
    public static function getConfiguredPostTypes(): array
    {
        $graph = self::build();
        return $graph['configured'];
    }

    /**
     * Configured post types + all relationship slugs referenced in settings.
     *
     * @return string[]
     */
    public static function getWatchedPostTypes(): array
    {
        $graph = self::build();
        return $graph['watched'];
    }

    /**
     * Build the relationship graph projection from settings.
     *
     * @return array{forward:array<string,string[]>,inverted:array<string,string[]>,configured:string[],watched:string[]}
     */
    public static function build(): array
    {
        if (self::$graphCache !== null) {
            return self::$graphCache;
        }

        $option = InvalidatorRuntime::settingsRepository()->getOption();
        $postTypes = $option['post_types'] ?? [];
        if (!is_array($postTypes)) {
            $postTypes = [];
        }

        $forward = [];
        $inverted = [];

        foreach ($postTypes as $hostPostType => $hostConfig) {
            $hostPostType = sanitize_key((string) $hostPostType);
            if ($hostPostType === '') {
                continue;
            }

            $relationships = [];
            if (is_array($hostConfig)) {
                $relationshipsIn = $hostConfig['relationships'] ?? [];
                if (is_array($relationshipsIn)) {
                    foreach ($relationshipsIn as $relatedPostType) {
                        $relatedPostType = sanitize_key((string) $relatedPostType);
                        if ($relatedPostType === '') {
                            continue;
                        }

                        $relationships[] = $relatedPostType;
                    }
                }
            }

            $relationships = array_values(array_unique($relationships));
            sort($relationships, SORT_STRING);
            $forward[$hostPostType] = $relationships;

            foreach ($relationships as $relatedPostType) {
                $inverted[$relatedPostType][] = $hostPostType;
            }
        }

        foreach ($inverted as $relatedPostType => $hosts) {
            $hosts = array_values(array_unique(array_filter(array_map(
                static fn($host): string => sanitize_key((string) $host),
                $hosts
            ))));
            sort($hosts, SORT_STRING);
            $inverted[$relatedPostType] = $hosts;
        }

        $configured = array_keys($forward);
        sort($configured, SORT_STRING);

        $watchedLookup = array_fill_keys($configured, true);
        foreach ($forward as $relationships) {
            foreach ($relationships as $relatedPostType) {
                $watchedLookup[$relatedPostType] = true;
            }
        }

        $watched = array_keys($watchedLookup);
        sort($watched, SORT_STRING);

        self::$graphCache = [
            'forward' => $forward,
            'inverted' => $inverted,
            'configured' => $configured,
            'watched' => $watched,
        ];

        return self::$graphCache;
    }
}
