<?php

declare(strict_types=1);

namespace App\Support\Governance;

use App\Support\Inventory\InventoryPolicyTypeMeta;
use App\Support\OperationCatalog;
use App\Support\Providers\ProviderReasonCodes;

final class PlatformVocabularyGlossary
{
    public const string BOUNDARY_PLATFORM_CORE = 'platform_core';

    public const string BOUNDARY_CROSS_DOMAIN_GOVERNANCE = 'cross_domain_governance';

    public const string BOUNDARY_INTUNE_SPECIFIC = 'intune_specific';

    public const string OWNER_PLATFORM_CORE = 'platform_core';

    public const string OWNER_DOMAIN_OWNED = 'domain_owned';

    public const string OWNER_PROVIDER_OWNED = 'provider_owned';

    public const string OWNER_COMPATIBILITY_ALIAS = 'compatibility_alias';

    public const string OWNER_COMPATIBILITY_ONLY = 'compatibility_only';

    /**
     * @return list<PlatformVocabularyTerm>
     */
    public function terms(): array
    {
        return array_values(array_map(
            static fn (array $term): PlatformVocabularyTerm => PlatformVocabularyTerm::fromArray($term),
            $this->configuredTerms(),
        ));
    }

    public function term(string $term): ?PlatformVocabularyTerm
    {
        $normalized = trim(mb_strtolower($term));

        foreach ($this->terms() as $candidate) {
            if (trim(mb_strtolower($candidate->termKey)) === $normalized) {
                return $candidate;
            }
        }

        return $this->resolveAlias($term);
    }

    public function resolveAlias(string $term, ?string $context = null): ?PlatformVocabularyTerm
    {
        $normalized = trim(mb_strtolower($term));
        $normalizedContext = is_string($context) ? trim(mb_strtolower($context)) : null;

        foreach ($this->terms() as $candidate) {
            $aliases = array_map(
                static fn (string $alias): string => trim(mb_strtolower($alias)),
                $candidate->legacyAliases,
            );

            if (! in_array($normalized, $aliases, true)) {
                continue;
            }

            if ($normalizedContext !== null && $candidate->allowedContexts !== []) {
                $contexts = array_map(
                    static fn (string $allowedContext): string => trim(mb_strtolower($allowedContext)),
                    $candidate->allowedContexts,
                );

                if (! in_array($normalizedContext, $contexts, true)) {
                    continue;
                }
            }

            return $candidate;
        }

        return null;
    }

    public function canonicalName(string $term): ?string
    {
        return $this->term($term)?->termKey;
    }

    public function isCanonical(string $term): bool
    {
        $resolved = $this->term($term);

        if (! $resolved instanceof PlatformVocabularyTerm) {
            return false;
        }

        return trim(mb_strtolower($term)) === trim(mb_strtolower($resolved->termKey));
    }

    public function ownership(string $term): ?string
    {
        return $this->term($term)?->ownerLayer;
    }

    /**
     * @return list<string>
     */
    public function canonicalTerms(): array
    {
        return array_values(array_map(
            static fn (PlatformVocabularyTerm $term): string => $term->termKey,
            $this->terms(),
        ));
    }

    /**
     * @return array<string, array{
     *     term_key: string,
     *     canonical_label: string,
     *     canonical_description: string,
     *     boundary_classification: string,
     *     owner_layer: string,
     *     allowed_contexts: list<string>,
     *     legacy_aliases: list<string>,
     *     alias_retirement_path: ?string,
     *     forbidden_platform_aliases: list<string>
     * }>
     */
    public function termInventory(): array
    {
        return collect($this->terms())
            ->mapWithKeys(static fn (PlatformVocabularyTerm $term): array => [
                $term->termKey => $term->toArray(),
            ])
            ->all();
    }

    /**
     * @return array<string, list<string>>
     */
    public function legacyAliases(): array
    {
        return collect($this->terms())
            ->filter(static fn (PlatformVocabularyTerm $term): bool => $term->legacyAliases !== [])
            ->mapWithKeys(static fn (PlatformVocabularyTerm $term): array => [
                $term->termKey => $term->legacyAliases,
            ])
            ->all();
    }

    /**
     * @return array<string, array{
     *     canonical_name: string,
     *     legacy_aliases: list<string>,
     *     retirement_path: ?string,
     *     forbidden_platform_aliases: list<string>
     * }>
     */
    public function aliasRetirementInventory(): array
    {
        return collect($this->terms())
            ->filter(static fn (PlatformVocabularyTerm $term): bool => $term->legacyAliases !== [])
            ->mapWithKeys(static fn (PlatformVocabularyTerm $term): array => [
                $term->termKey => [
                    'canonical_name' => $term->termKey,
                    'legacy_aliases' => $term->legacyAliases,
                    'retirement_path' => $term->aliasRetirementPath,
                    'forbidden_platform_aliases' => $term->forbiddenPlatformAliases,
                ],
            ])
            ->all();
    }

    public function boundaryClassification(string $term): ?string
    {
        return $this->term($term)?->boundaryClassification;
    }

    /**
     * @return list<string>
     */
    public function allowedBoundaryClassifications(): array
    {
        return [
            self::BOUNDARY_PLATFORM_CORE,
            self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
            self::BOUNDARY_INTUNE_SPECIFIC,
        ];
    }

    /**
     * @return list<RegistryOwnershipDescriptor>
     */
    public function registries(): array
    {
        return array_values(array_map(
            static fn (array $descriptor): RegistryOwnershipDescriptor => RegistryOwnershipDescriptor::fromArray($descriptor),
            $this->configuredRegistries(),
        ));
    }

    public function registry(string $registryKey): ?RegistryOwnershipDescriptor
    {
        $normalized = trim(mb_strtolower($registryKey));

        foreach ($this->registries() as $descriptor) {
            if (trim(mb_strtolower($descriptor->registryKey)) === $normalized) {
                return $descriptor;
            }
        }

        return null;
    }

    /**
     * @return array<string, array{
     *     registry_key: string,
     *     boundary_classification: string,
     *     owner_layer: string,
     *     source_class_or_file: string,
     *     canonical_nouns: list<string>,
     *     allowed_consumers: list<string>,
     *     compatibility_notes: ?string
     * }>
     */
    public function registryInventory(): array
    {
        return collect($this->registries())
            ->mapWithKeys(static fn (RegistryOwnershipDescriptor $descriptor): array => [
                $descriptor->registryKey => $descriptor->toArray(),
            ])
            ->all();
    }

    /**
     * @return array<string, array{
     *     owner_namespace: string,
     *     boundary_classification: string,
     *     owner_layer: string,
     *     compatibility_notes: ?string
     * }>
     */
    public function reasonNamespaceInventory(): array
    {
        return $this->configuredReasonNamespaces();
    }

    /**
     * @return array{
     *     owner_namespace: string,
     *     boundary_classification: string,
     *     owner_layer: string,
     *     compatibility_notes: ?string
     * }|null
     */
    public function reasonNamespace(string $ownerNamespace): ?array
    {
        $normalized = trim(mb_strtolower($ownerNamespace));

        foreach ($this->reasonNamespaceInventory() as $descriptor) {
            $candidate = is_string($descriptor['owner_namespace'] ?? null)
                ? trim(mb_strtolower((string) $descriptor['owner_namespace']))
                : null;

            if ($candidate === $normalized) {
                return $descriptor;
            }
        }

        return null;
    }

    public function classifyReasonNamespace(string $ownerNamespace): ?string
    {
        return $this->reasonNamespace($ownerNamespace)['boundary_classification'] ?? null;
    }

    public function classifyOperationType(string $operationType): ?string
    {
        if (trim($operationType) === '') {
            return null;
        }

        return $this->registry('operation_catalog')?->boundaryClassification;
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function configuredTerms(): array
    {
        $configured = config('tenantpilot.platform_vocabulary.terms');

        if (is_array($configured) && $configured !== []) {
            return $configured;
        }

        return [
            'governed_subject' => [
                'term_key' => 'governed_subject',
                'canonical_label' => 'Governed subject',
                'canonical_description' => 'The platform-facing noun for a governed object across compare, snapshot, evidence, and review surfaces.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['compare', 'snapshot', 'evidence', 'review', 'reporting'],
                'legacy_aliases' => ['policy_type'],
                'alias_retirement_path' => 'Retire false-universal policy_type wording from platform-owned summaries and descriptors while preserving Intune-owned storage.',
                'forbidden_platform_aliases' => ['policy_type'],
            ],
            'domain_key' => [
                'term_key' => 'domain_key',
                'canonical_label' => 'Governance domain',
                'canonical_description' => 'The canonical domain discriminator for cross-domain and platform-near contracts.',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['governance', 'compare', 'snapshot', 'reporting'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'subject_class' => [
                'term_key' => 'subject_class',
                'canonical_label' => 'Subject class',
                'canonical_description' => 'The canonical subject-class discriminator for governed-subject contracts.',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['governance', 'compare', 'snapshot'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'subject_type_key' => [
                'term_key' => 'subject_type_key',
                'canonical_label' => 'Governed subject key',
                'canonical_description' => 'The domain-owned subject-family key used by platform-near descriptors.',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['governance', 'compare', 'snapshot', 'evidence'],
                'legacy_aliases' => ['policy_type'],
                'alias_retirement_path' => 'Prefer subject_type_key on platform-near payloads and keep policy_type only where the owning model is Intune-specific.',
                'forbidden_platform_aliases' => ['policy_type'],
            ],
            'subject_type_label' => [
                'term_key' => 'subject_type_label',
                'canonical_label' => 'Governed subject label',
                'canonical_description' => 'The operator-facing label for a governed subject family.',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['snapshot', 'evidence', 'compare', 'review'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'resource_type' => [
                'term_key' => 'resource_type',
                'canonical_label' => 'Resource type',
                'canonical_description' => 'Optional resource-shaped noun for platform-facing summaries when a governed subject also needs a resource family label.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['reporting', 'review'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'operation_type' => [
                'term_key' => 'operation_type',
                'canonical_label' => 'Operation type',
                'canonical_description' => 'The canonical platform operation identifier shown on monitoring and launch surfaces.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['monitoring', 'reporting', 'launch_surfaces'],
                'legacy_aliases' => ['type'],
                'alias_retirement_path' => 'Expose canonical operation_type on read models while operation_runs.type remains a compatibility storage seam during rollout.',
                'forbidden_platform_aliases' => [],
            ],
            'platform_reason_family' => [
                'term_key' => 'platform_reason_family',
                'canonical_label' => 'Platform reason family',
                'canonical_description' => 'The cross-domain platform family that explains the top-level cause category without erasing domain ownership.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['reason_translation', 'monitoring', 'review', 'reporting'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'reason_owner.owner_namespace' => [
                'term_key' => 'reason_owner.owner_namespace',
                'canonical_label' => 'Reason owner namespace',
                'canonical_description' => 'The explicit namespace that marks whether a translated reason is provider-owned, governance-owned, access-owned, or runtime-owned.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['reason_translation', 'review', 'reporting'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'reason_code' => [
                'term_key' => 'reason_code',
                'canonical_label' => 'Reason code',
                'canonical_description' => 'The original domain-owned or provider-owned reason identifier preserved for diagnostics and translation lookup.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['reason_translation', 'diagnostics'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'registry_key' => [
                'term_key' => 'registry_key',
                'canonical_label' => 'Registry key',
                'canonical_description' => 'The stable identifier for a contributor-facing registry or catalog.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['governance', 'contributor_guidance'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'boundary_classification' => [
                'term_key' => 'boundary_classification',
                'canonical_label' => 'Boundary classification',
                'canonical_description' => 'The explicit classification that marks a term or registry as platform_core, cross_domain_governance, or intune_specific.',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'allowed_contexts' => ['governance', 'contributor_guidance'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => [],
            ],
            'policy_type' => [
                'term_key' => 'policy_type',
                'canonical_label' => 'Intune policy type',
                'canonical_description' => 'The Intune-specific discriminator that remains valid on adapter-owned or Intune-owned models but must not be treated as a universal platform noun.',
                'boundary_classification' => self::BOUNDARY_INTUNE_SPECIFIC,
                'owner_layer' => self::OWNER_DOMAIN_OWNED,
                'allowed_contexts' => ['intune_adapter', 'intune_inventory', 'intune_backup'],
                'legacy_aliases' => [],
                'alias_retirement_path' => null,
                'forbidden_platform_aliases' => ['governed_subject'],
            ],
        ];
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    private function configuredRegistries(): array
    {
        $configured = config('tenantpilot.platform_vocabulary.registries');

        if (is_array($configured) && $configured !== []) {
            return $configured;
        }

        return [
            'governance_subject_taxonomy_registry' => [
                'registry_key' => 'governance_subject_taxonomy_registry',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'source_class_or_file' => GovernanceSubjectTaxonomyRegistry::class,
                'canonical_nouns' => ['domain_key', 'subject_class', 'subject_type_key', 'subject_type_label'],
                'allowed_consumers' => ['baseline_scope', 'compare', 'snapshot', 'review'],
                'compatibility_notes' => 'Provides the governed-subject source of truth, resolves legacy policy-type payloads, and preserves inactive or future-domain entries without exposing them as active operator choices.',
            ],
            'operation_catalog' => [
                'registry_key' => 'operation_catalog',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'source_class_or_file' => OperationCatalog::class,
                'canonical_nouns' => ['operation_type'],
                'allowed_consumers' => ['monitoring', 'reporting', 'launch_surfaces', 'audit'],
                'compatibility_notes' => 'Resolves canonical operation meaning from historical storage values without treating every stored raw string as equally canonical.',
            ],
            'provider_reason_codes' => [
                'registry_key' => 'provider_reason_codes',
                'boundary_classification' => self::BOUNDARY_INTUNE_SPECIFIC,
                'owner_layer' => self::OWNER_PROVIDER_OWNED,
                'source_class_or_file' => ProviderReasonCodes::class,
                'canonical_nouns' => ['reason_code'],
                'allowed_consumers' => ['reason_translation'],
                'compatibility_notes' => 'Provider-owned reason codes remain namespaced domain details and become platform-safe only through the reason-translation boundary.',
            ],
            'inventory_policy_type_catalog' => [
                'registry_key' => 'inventory_policy_type_catalog',
                'boundary_classification' => self::BOUNDARY_INTUNE_SPECIFIC,
                'owner_layer' => self::OWNER_DOMAIN_OWNED,
                'source_class_or_file' => InventoryPolicyTypeMeta::class,
                'canonical_nouns' => ['policy_type'],
                'allowed_consumers' => ['intune_adapter', 'inventory', 'backup'],
                'compatibility_notes' => 'The supported Intune policy-type list is not a universal platform registry and must be wrapped before reuse on platform-near summaries.',
            ],
        ];
    }

    /**
     * @return array<string, array{
     *     owner_namespace: string,
     *     boundary_classification: string,
     *     owner_layer: string,
     *     compatibility_notes: ?string
     * }>
     */
    private function configuredReasonNamespaces(): array
    {
        $configured = config('tenantpilot.platform_vocabulary.reason_namespaces');

        if (is_array($configured) && $configured !== []) {
            return $configured;
        }

        return [
            'tenant_operability' => [
                'owner_namespace' => 'tenant_operability',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'compatibility_notes' => 'ManagedEnvironment operability reasons are platform-core guardrails for workspace and tenant context.',
            ],
            'execution_denial' => [
                'owner_namespace' => 'execution_denial',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'compatibility_notes' => 'Execution denial reasons remain platform-core run-legitimacy semantics.',
            ],
            'operation_lifecycle' => [
                'owner_namespace' => 'operation_lifecycle',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'compatibility_notes' => 'Lifecycle reconciliation reasons remain platform-core monitoring semantics.',
            ],
            'governance.baseline_compare' => [
                'owner_namespace' => 'governance.baseline_compare',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_DOMAIN_OWNED,
                'compatibility_notes' => 'Baseline-compare reason codes are governance-owned details translated through the platform boundary.',
            ],
            'governance.artifact_truth' => [
                'owner_namespace' => 'governance.artifact_truth',
                'boundary_classification' => self::BOUNDARY_CROSS_DOMAIN_GOVERNANCE,
                'owner_layer' => self::OWNER_DOMAIN_OWNED,
                'compatibility_notes' => 'Artifact-truth reason codes remain governance-owned and are surfaced through platform-safe summaries.',
            ],
            'provider.microsoft_graph' => [
                'owner_namespace' => 'provider.microsoft_graph',
                'boundary_classification' => self::BOUNDARY_INTUNE_SPECIFIC,
                'owner_layer' => self::OWNER_PROVIDER_OWNED,
                'compatibility_notes' => 'Microsoft Graph provider reasons remain provider-owned and Intune-specific.',
            ],
            'provider.intune_rbac' => [
                'owner_namespace' => 'provider.intune_rbac',
                'boundary_classification' => self::BOUNDARY_INTUNE_SPECIFIC,
                'owner_layer' => self::OWNER_PROVIDER_OWNED,
                'compatibility_notes' => 'Provider-owned Intune RBAC reasons remain Intune-specific.',
            ],
            'rbac.intune' => [
                'owner_namespace' => 'rbac.intune',
                'boundary_classification' => self::BOUNDARY_INTUNE_SPECIFIC,
                'owner_layer' => self::OWNER_DOMAIN_OWNED,
                'compatibility_notes' => 'RBAC detail remains Intune-specific domain context.',
            ],
            'reason_translation.fallback' => [
                'owner_namespace' => 'reason_translation.fallback',
                'boundary_classification' => self::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => self::OWNER_PLATFORM_CORE,
                'compatibility_notes' => 'Fallback translation remains a platform-core compatibility seam until a domain owner is known.',
            ],
        ];
    }
}