<?php

declare(strict_types=1);

namespace App\Support\Governance;

use Illuminate\Support\Str;

final class PlatformSubjectDescriptorNormalizer
{
    public function __construct(
        private readonly GovernanceSubjectTaxonomyRegistry $registry,
        private readonly PlatformVocabularyGlossary $glossary,
    ) {}

    /**
     * @param  array<string, mixed>  $payload
     */
    public function fromArray(array $payload, string $sourceSurface = 'runtime'): SubjectDescriptorNormalizationResult
    {
        $usedLegacySource = ! isset($payload['subject_type_key'])
            && (isset($payload['policy_type']) || isset($payload['subject_type']));

        $subjectTypeKey = $this->stringValue(
            $payload['subject_type_key']
            ?? $payload['policy_type']
            ?? $payload['subject_type']
            ?? null,
        );

        $result = $this->normalize(
            subjectTypeKey: $subjectTypeKey,
            domainKey: $this->stringValue($payload['domain_key'] ?? null),
            subjectClass: $this->stringValue($payload['subject_class'] ?? null),
            legacyPolicyType: $this->stringValue($payload['policy_type'] ?? null),
            sourceSurface: $sourceSurface,
        );

        if (! $usedLegacySource || $result->usedLegacyAlias) {
            return $result;
        }

        return new SubjectDescriptorNormalizationResult(
            descriptor: $result->descriptor,
            sourceSurface: $result->sourceSurface,
            usedLegacyAlias: true,
            warnings: array_values(array_unique(array_merge(
                ['Resolved a compatibility-only policy_type payload through governed-subject normalization.'],
                $result->warnings,
            ))),
        );
    }

    public function fromLegacyBucket(string $legacyBucket, string $subjectTypeKey, string $sourceSurface = 'runtime'): SubjectDescriptorNormalizationResult
    {
        $subjectType = $this->registry->findBySubjectTypeKey($subjectTypeKey, $legacyBucket);

        return $this->buildResult(
            subjectType: $subjectType,
            sourceSurface: $sourceSurface,
            legacyPolicyType: $subjectTypeKey,
            usedLegacyAlias: true,
        );
    }

    public function normalize(
        ?string $subjectTypeKey,
        ?string $domainKey = null,
        ?string $subjectClass = null,
        ?string $legacyPolicyType = null,
        string $sourceSurface = 'runtime',
    ): SubjectDescriptorNormalizationResult {
        $subjectType = null;

        if (is_string($subjectTypeKey) && trim($subjectTypeKey) !== '') {
            $subjectType = $domainKey !== null
                ? $this->registry->find($domainKey, $subjectTypeKey)
                : $this->registry->findBySubjectTypeKey($subjectTypeKey);
        }

        if ($subjectType === null && is_string($legacyPolicyType) && trim($legacyPolicyType) !== '') {
            return $this->fromLegacyBucket('policy_types', $legacyPolicyType, $sourceSurface);
        }

        return $this->buildResult(
            subjectType: $subjectType,
            sourceSurface: $sourceSurface,
            explicitDomainKey: $domainKey,
            explicitSubjectClass: $subjectClass,
            legacyPolicyType: $legacyPolicyType,
            usedLegacyAlias: false,
        );
    }

    /**
     * @param  list<array{domain_key: string, subject_class: string, subject_type_keys: list<string>, filters: array<string, mixed>}>  $entries
     * @return list<array<string, mixed>>
     */
    public function descriptorsForScopeEntries(array $entries): array
    {
        $descriptors = [];

        foreach ($entries as $entry) {
            foreach ($entry['subject_type_keys'] as $subjectTypeKey) {
                $result = $this->normalize(
                    subjectTypeKey: $subjectTypeKey,
                    domainKey: $entry['domain_key'],
                    subjectClass: $entry['subject_class'],
                    legacyPolicyType: $subjectTypeKey,
                    sourceSurface: 'baseline_scope',
                );

                $descriptors[] = $result->toArray();
            }
        }

        return $descriptors;
    }

    private function buildResult(
        ?GovernanceSubjectType $subjectType,
        string $sourceSurface,
        ?string $explicitDomainKey = null,
        ?string $explicitSubjectClass = null,
        ?string $legacyPolicyType = null,
        bool $usedLegacyAlias = false,
    ): SubjectDescriptorNormalizationResult {
        $platformNoun = $this->glossary->term('governed_subject')?->canonicalLabel ?? 'Governed subject';
        $warnings = [];

        if (! $subjectType instanceof GovernanceSubjectType) {
            $warnings[] = 'Governed-subject descriptor fell back to compatibility-only naming because the subject type could not be resolved in the taxonomy registry.';

            return new SubjectDescriptorNormalizationResult(
                descriptor: new PlatformSubjectDescriptor(
                    domainKey: $explicitDomainKey ?? GovernanceDomainKey::Intune->value,
                    subjectClass: $explicitSubjectClass ?? GovernanceSubjectClass::Policy->value,
                    subjectTypeKey: $legacyPolicyType ?? 'unknown_subject',
                    subjectTypeLabel: Str::headline($legacyPolicyType ?? 'Unknown subject'),
                    platformNoun: $platformNoun,
                    displayLabel: Str::headline($legacyPolicyType ?? 'Unknown subject'),
                    legacyPolicyType: $legacyPolicyType,
                ),
                sourceSurface: $sourceSurface,
                usedLegacyAlias: true,
                warnings: $warnings,
            );
        }

        if ($usedLegacyAlias) {
            $warnings[] = sprintf(
                'Resolved legacy subject alias "%s" through the governed-subject taxonomy registry for %s.',
                $legacyPolicyType ?? $subjectType->subjectTypeKey,
                $sourceSurface,
            );
        }

        return new SubjectDescriptorNormalizationResult(
            descriptor: new PlatformSubjectDescriptor(
                domainKey: $subjectType->domainKey->value,
                subjectClass: $subjectType->subjectClass->value,
                subjectTypeKey: $subjectType->subjectTypeKey,
                subjectTypeLabel: $subjectType->label,
                platformNoun: $platformNoun,
                displayLabel: $subjectType->label,
                legacyPolicyType: $legacyPolicyType,
            ),
            sourceSurface: $sourceSurface,
            usedLegacyAlias: $usedLegacyAlias,
            warnings: $warnings,
        );
    }

    private function stringValue(mixed $value): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $trimmed = trim($value);

        return $trimmed !== '' ? $trimmed : null;
    }
}