<?php

declare(strict_types=1);

namespace App\Support\Governance;

use App\Support\Inventory\InventoryPolicyTypeMeta;

class GovernanceSubjectTaxonomyRegistry
{
    /**
     * @var array<string, list<string>>
     */
    private const DOMAIN_CLASSES = [
        GovernanceDomainKey::Intune->value => [GovernanceSubjectClass::Policy->value],
        GovernanceDomainKey::PlatformFoundation->value => [GovernanceSubjectClass::ConfigurationResource->value],
        GovernanceDomainKey::Entra->value => [GovernanceSubjectClass::Control->value],
    ];

    /**
     * @return list<GovernanceSubjectType>
     */
    public function all(): array
    {
        return array_values(array_merge(
            $this->policySubjectTypes(),
            $this->foundationSubjectTypes(),
        ));
    }

    /**
     * @return list<GovernanceSubjectType>
     */
    public function active(): array
    {
        return array_values(array_filter(
            $this->all(),
            static fn (GovernanceSubjectType $subjectType): bool => $subjectType->active,
        ));
    }

    /**
     * @return list<string>
     */
    public function activeLegacyBucketKeys(string $legacyBucket): array
    {
        $subjectTypes = array_filter(
            $this->active(),
            static fn (GovernanceSubjectType $subjectType): bool => $subjectType->legacyBucket === $legacyBucket,
        );

        $keys = array_map(
            static fn (GovernanceSubjectType $subjectType): string => $subjectType->subjectTypeKey,
            $subjectTypes,
        );

        sort($keys, SORT_STRING);

        return array_values(array_unique($keys));
    }

    public function find(string $domainKey, string $subjectTypeKey): ?GovernanceSubjectType
    {
        foreach ($this->all() as $subjectType) {
            if ($subjectType->domainKey->value !== trim($domainKey)) {
                continue;
            }

            if ($subjectType->subjectTypeKey !== trim($subjectTypeKey)) {
                continue;
            }

            return $subjectType;
        }

        return null;
    }

    public function findBySubjectTypeKey(string $subjectTypeKey, ?string $legacyBucket = null): ?GovernanceSubjectType
    {
        $subjectTypeKey = trim($subjectTypeKey);
        $legacyBucket = is_string($legacyBucket) ? trim($legacyBucket) : null;

        foreach ($this->all() as $subjectType) {
            if ($subjectType->subjectTypeKey !== $subjectTypeKey) {
                continue;
            }

            if ($legacyBucket !== null && $subjectType->legacyBucket !== $legacyBucket) {
                continue;
            }

            return $subjectType;
        }

        return null;
    }

    /**
     * @return list<string>
     */
    public function canonicalNouns(): array
    {
        return ['domain_key', 'subject_class', 'subject_type_key', 'subject_type_label'];
    }

    public function ownershipDescriptor(?PlatformVocabularyGlossary $glossary = null): RegistryOwnershipDescriptor
    {
        $glossary ??= app(PlatformVocabularyGlossary::class);

        return $glossary->registry('governance_subject_taxonomy_registry')
            ?? RegistryOwnershipDescriptor::fromArray([
                'registry_key' => 'governance_subject_taxonomy_registry',
                'boundary_classification' => PlatformVocabularyGlossary::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => PlatformVocabularyGlossary::OWNER_PLATFORM_CORE,
                'source_class_or_file' => self::class,
                'canonical_nouns' => $this->canonicalNouns(),
                'allowed_consumers' => ['baseline_scope', 'compare', 'snapshot', 'review'],
                'compatibility_notes' => 'Governed-subject registry lookups remain the canonical bridge from legacy policy-type payloads to platform-safe descriptors.',
            ]);
    }

    public function isKnownDomain(string $domainKey): bool
    {
        return array_key_exists(trim($domainKey), self::DOMAIN_CLASSES);
    }

    public function allowsSubjectClass(string $domainKey, string $subjectClass): bool
    {
        $domainKey = trim($domainKey);
        $subjectClass = trim($subjectClass);

        return in_array($subjectClass, self::DOMAIN_CLASSES[$domainKey] ?? [], true);
    }

    public function supportsFilters(string $domainKey, string $subjectClass): bool
    {
        return false;
    }

    public function groupLabel(string $domainKey, string $subjectClass): string
    {
        return match ([trim($domainKey), trim($subjectClass)]) {
            [GovernanceDomainKey::Intune->value, GovernanceSubjectClass::Policy->value] => __('localization.policy.taxonomy.policies'),
            [GovernanceDomainKey::PlatformFoundation->value, GovernanceSubjectClass::ConfigurationResource->value] => 'Platform foundation configuration resources',
            [GovernanceDomainKey::Entra->value, GovernanceSubjectClass::Control->value] => 'Entra controls',
            default => trim($domainKey).' / '.trim($subjectClass),
        };
    }

    /**
     * @return list<GovernanceSubjectType>
     */
    private function policySubjectTypes(): array
    {
        return array_values(array_map(
            function (array $row): GovernanceSubjectType {
                $type = (string) ($row['type'] ?? '');
                $label = (string) ($row['label'] ?? $type);
                $category = is_string($row['category'] ?? null) ? (string) $row['category'] : null;
                $platform = is_string($row['platform'] ?? null) ? (string) $row['platform'] : null;
                $contract = InventoryPolicyTypeMeta::baselineSupportContract($type);

                return new GovernanceSubjectType(
                    domainKey: GovernanceDomainKey::Intune,
                    subjectClass: GovernanceSubjectClass::Policy,
                    subjectTypeKey: $type,
                    label: $label,
                    description: $this->descriptionFor($category, $platform),
                    captureSupported: in_array($contract['capture_capability'] ?? null, ['supported', 'limited'], true),
                    compareSupported: in_array($contract['compare_capability'] ?? null, ['supported', 'limited'], true),
                    inventorySupported: true,
                    active: true,
                    supportMode: $this->supportModeForContract($contract),
                    legacyBucket: 'policy_types',
                );
            },
            array_values(array_filter(
                InventoryPolicyTypeMeta::supported(),
                static fn (array $row): bool => filled($row['type'] ?? null),
            )),
        ));
    }

    /**
     * @return list<GovernanceSubjectType>
     */
    private function foundationSubjectTypes(): array
    {
        return array_values(array_map(
            function (array $row): GovernanceSubjectType {
                $type = (string) ($row['type'] ?? '');
                $label = InventoryPolicyTypeMeta::baselineCompareLabel($type) ?? (string) ($row['label'] ?? $type);
                $category = is_string($row['category'] ?? null) ? (string) $row['category'] : null;
                $platform = is_string($row['platform'] ?? null) ? (string) $row['platform'] : null;
                $contract = InventoryPolicyTypeMeta::baselineSupportContract($type);
                $supported = (bool) data_get($row, 'baseline_compare.supported', false);

                return new GovernanceSubjectType(
                    domainKey: GovernanceDomainKey::PlatformFoundation,
                    subjectClass: GovernanceSubjectClass::ConfigurationResource,
                    subjectTypeKey: $type,
                    label: $label,
                    description: $this->descriptionFor($category, $platform),
                    captureSupported: in_array($contract['capture_capability'] ?? null, ['supported', 'limited'], true),
                    compareSupported: in_array($contract['compare_capability'] ?? null, ['supported', 'limited'], true),
                    inventorySupported: in_array($contract['source_model_expected'] ?? null, ['inventory', 'policy'], true),
                    active: $supported,
                    supportMode: $this->supportModeForContract($contract),
                    legacyBucket: 'foundation_types',
                );
            },
            array_values(array_filter(
                InventoryPolicyTypeMeta::foundations(),
                static fn (array $row): bool => filled($row['type'] ?? null),
            )),
        ));
    }

    private function descriptionFor(?string $category, ?string $platform): ?string
    {
        $parts = array_values(array_filter([$category, $platform], static fn (?string $part): bool => filled($part)));

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

        return implode(' | ', $parts);
    }

    /**
     * @param  array<string, mixed>  $contract
     */
    private function supportModeForContract(array $contract): string
    {
        $capabilities = [
            (string) ($contract['capture_capability'] ?? 'unsupported'),
            (string) ($contract['compare_capability'] ?? 'unsupported'),
        ];

        if (! (bool) ($contract['runtime_valid'] ?? false) && (bool) ($contract['config_supported'] ?? false)) {
            return 'invalid_support_config';
        }

        if (in_array('supported', $capabilities, true)) {
            return 'supported';
        }

        if (in_array('limited', $capabilities, true)) {
            return 'limited';
        }

        return 'excluded';
    }
}