<?php

declare(strict_types=1);

namespace App\Support;

use App\Support\Governance\PlatformVocabularyGlossary;
use App\Support\Governance\RegistryOwnershipDescriptor;
use App\Support\OpsUx\OperationSummaryKeys;

final class OperationCatalog
{
    public const string TYPE_PERMISSION_POSTURE_CHECK = 'permission_posture_check';

    /**
     * @return array<string, string>
     */
    public static function labels(): array
    {
        $labels = [];

        foreach (self::operationAliases() as $alias) {
            $labels[$alias->rawValue] = self::canonicalDefinitions()[$alias->canonicalCode]->displayLabel;
        }

        return $labels;
    }

    /**
     * @return array<string, array{
     *     canonical_code: string,
     *     domain_key: ?string,
     *     artifact_family: ?string,
     *     display_label: string,
     *     supports_operator_explanation: bool,
     *     expected_duration_seconds: ?int
     * }>
     */
    public static function canonicalInventory(): array
    {
        $inventory = [];

        foreach (self::canonicalDefinitions() as $canonicalCode => $definition) {
            $inventory[$canonicalCode] = $definition->toArray();
        }

        return $inventory;
    }

    public static function label(string $operationType): string
    {
        $operationType = trim($operationType);

        if ($operationType === '') {
            return 'Operation';
        }

        return self::resolve($operationType)->canonical->displayLabel;
    }

    public static function expectedDurationSeconds(string $operationType): ?int
    {
        return self::resolve($operationType)->canonical->expectedDurationSeconds;
    }

    /**
     * @return array<int, string>
     */
    public static function allowedSummaryKeys(): array
    {
        return OperationSummaryKeys::all();
    }

    public static function governanceArtifactFamily(string $operationType): ?string
    {
        return self::resolve($operationType)->canonical->artifactFamily;
    }

    public static function isGovernanceArtifactOperation(string $operationType): bool
    {
        return self::governanceArtifactFamily($operationType) !== null;
    }

    public static function supportsOperatorExplanation(string $operationType): bool
    {
        return self::resolve($operationType)->canonical->supportsOperatorExplanation;
    }

    public static function canonicalCode(string $operationType): string
    {
        return self::resolve($operationType)->canonical->canonicalCode;
    }

    /**
     * @return list<string>
     */
    public static function canonicalNouns(): array
    {
        return ['operation_type'];
    }

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

        return $glossary->registry('operation_catalog')
            ?? RegistryOwnershipDescriptor::fromArray([
                'registry_key' => 'operation_catalog',
                'boundary_classification' => PlatformVocabularyGlossary::BOUNDARY_PLATFORM_CORE,
                'owner_layer' => PlatformVocabularyGlossary::OWNER_PLATFORM_CORE,
                'source_class_or_file' => self::class,
                'canonical_nouns' => self::canonicalNouns(),
                '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.',
            ]);
    }

    public static function boundaryClassification(?PlatformVocabularyGlossary $glossary = null): string
    {
        return self::ownershipDescriptor($glossary)->boundaryClassification;
    }

    /**
     * Read-side compatibility helper for historical rows only.
     *
     * Writers must emit the canonical code directly instead of translating a
     * legacy alias through this method.
     *
     * @return list<string>
     */
    public static function rawValuesForCanonical(string $canonicalCode): array
    {
        $canonicalCode = trim($canonicalCode);
        $values = array_values(array_map(
            static fn (OperationTypeAlias $alias): string => $alias->rawValue,
            array_filter(
                self::operationAliases(),
                static fn (OperationTypeAlias $alias): bool => $alias->canonicalCode === $canonicalCode,
            ),
        ));

        if (array_key_exists($canonicalCode, self::canonicalDefinitions()) && ! in_array($canonicalCode, $values, true)) {
            $values[] = $canonicalCode;
        }

        return $values;
    }

    /**
     * @param  iterable<mixed>|null  $types
     * @return array<string, string>
     */
    public static function filterOptions(?iterable $types = null): array
    {
        $values = collect($types ?? array_keys(self::labels()))
            ->filter(static fn (mixed $type): bool => is_string($type) && trim($type) !== '')
            ->map(static fn (string $type): string => trim($type))
            ->mapWithKeys(static fn (string $type): array => [self::canonicalCode($type) => self::label($type)])
            ->sortBy(static fn (string $label): string => $label)
            ->all();

        return $values;
    }

    /**
     * @return array<string, array{
     *     raw_value: string,
     *     canonical_name: string,
     *     alias_status: string,
     *     write_allowed: bool,
     *     deprecation_note: ?string,
     *     retirement_path: ?string
     * }>
     */
    public static function aliasInventory(): array
    {
        $inventory = [];

        foreach (self::operationAliases() as $alias) {
            $inventory[$alias->rawValue] = $alias->retirementMetadata();
        }

        return $inventory;
    }

    public static function resolve(string $operationType): OperationTypeResolution
    {
        $operationType = trim($operationType);
        $aliases = self::operationAliases();
        $matchedAlias = collect($aliases)
            ->first(static fn (OperationTypeAlias $alias): bool => $alias->rawValue === $operationType);

        if ($matchedAlias instanceof OperationTypeAlias) {
            return new OperationTypeResolution(
                rawValue: $operationType,
                canonical: self::canonicalDefinitions()[$matchedAlias->canonicalCode],
                aliasesConsidered: array_values(array_filter(
                    $aliases,
                    static fn (OperationTypeAlias $alias): bool => $alias->canonicalCode === $matchedAlias->canonicalCode,
                )),
                aliasStatus: $matchedAlias->aliasStatus,
                wasLegacyAlias: $matchedAlias->aliasStatus !== 'canonical',
            );
        }

        $definition = self::canonicalDefinitions()[$operationType] ?? null;

        if ($definition instanceof CanonicalOperationType) {
            return new OperationTypeResolution(
                rawValue: $operationType,
                canonical: $definition,
                aliasesConsidered: array_values(array_filter(
                    $aliases,
                    static fn (OperationTypeAlias $alias): bool => $alias->canonicalCode === $operationType,
                )),
                aliasStatus: 'canonical',
                wasLegacyAlias: false,
            );
        }

        return new OperationTypeResolution(
            rawValue: $operationType,
            canonical: new CanonicalOperationType(
                canonicalCode: $operationType,
                domainKey: null,
                artifactFamily: null,
                displayLabel: 'Unknown operation',
                supportsOperatorExplanation: false,
                expectedDurationSeconds: null,
            ),
            aliasesConsidered: [],
            aliasStatus: 'unknown',
            wasLegacyAlias: false,
        );
    }

    /**
     * @return array<string, CanonicalOperationType>
     */
    private static function canonicalDefinitions(): array
    {
        return [
            'policy.sync' => new CanonicalOperationType('policy.sync', 'intune', null, 'Policy sync', false, 90),
            'policy.snapshot' => new CanonicalOperationType('policy.snapshot', 'intune', null, 'Policy snapshot', false, 120),
            'policy.delete' => new CanonicalOperationType('policy.delete', 'intune', null, 'Delete policies'),
            'policy.restore' => new CanonicalOperationType('policy.restore', 'intune', null, 'Restore policies'),
            'policy.export' => new CanonicalOperationType('policy.export', 'intune', null, 'Export policies to backup', false, 120),
            'provider.connection.check' => new CanonicalOperationType('provider.connection.check', 'intune', null, 'Provider connection check', false, 30),
            'inventory.sync' => new CanonicalOperationType('inventory.sync', 'intune', null, 'Inventory sync', false, 180),
            'compliance.snapshot' => new CanonicalOperationType('compliance.snapshot', 'intune', null, 'Compliance snapshot', false, 180),
            'directory.groups.sync' => new CanonicalOperationType('directory.groups.sync', 'entra', null, 'Directory groups sync', false, 120),
            'backup_set.update' => new CanonicalOperationType('backup_set.update', 'intune', null, 'Backup set update'),
            'backup_set.archive' => new CanonicalOperationType('backup_set.archive', 'intune', null, 'Archive backup sets'),
            'backup_set.restore' => new CanonicalOperationType('backup_set.restore', 'intune', null, 'Restore backup sets'),
            'backup_set.delete' => new CanonicalOperationType('backup_set.delete', 'intune', null, 'Delete backup sets'),
            'backup.schedule.execute' => new CanonicalOperationType('backup.schedule.execute', 'intune', null, 'Backup schedule run'),
            'backup.schedule.retention' => new CanonicalOperationType('backup.schedule.retention', 'intune', null, 'Backup schedule retention'),
            'backup.schedule.purge' => new CanonicalOperationType('backup.schedule.purge', 'intune', null, 'Backup schedule purge'),
            'restore.execute' => new CanonicalOperationType('restore.execute', 'intune', null, 'Restore execution'),
            'promotion.execute' => new CanonicalOperationType('promotion.execute', 'platform_foundation', null, 'Promotion execution', true, 120),
            'assignments.fetch' => new CanonicalOperationType('assignments.fetch', 'intune', null, 'Assignment fetch', false, 60),
            'assignments.restore' => new CanonicalOperationType('assignments.restore', 'intune', null, 'Assignment restore', false, 60),
            'ops.reconcile_adapter_runs' => new CanonicalOperationType('ops.reconcile_adapter_runs', 'platform_foundation', null, 'Reconcile adapter runs', false, 120),
            'directory.role_definitions.sync' => new CanonicalOperationType('directory.role_definitions.sync', 'entra', null, 'Role definitions sync'),
            'restore_run.delete' => new CanonicalOperationType('restore_run.delete', 'intune', null, 'Delete restore runs'),
            'restore_run.restore' => new CanonicalOperationType('restore_run.restore', 'intune', null, 'Restore restore runs'),
            'restore_run.force_delete' => new CanonicalOperationType('restore_run.force_delete', 'intune', null, 'Force delete restore runs'),
            'tenant.sync' => new CanonicalOperationType('tenant.sync', 'platform_foundation', null, 'ManagedEnvironment sync'),
            'policy_version.prune' => new CanonicalOperationType('policy_version.prune', 'intune', null, 'Prune policy versions'),
            'policy_version.restore' => new CanonicalOperationType('policy_version.restore', 'intune', null, 'Restore policy versions'),
            'policy_version.force_delete' => new CanonicalOperationType('policy_version.force_delete', 'intune', null, 'Delete policy versions'),
            'alerts.evaluate' => new CanonicalOperationType('alerts.evaluate', 'platform_foundation', null, 'Alerts evaluation', false, 120),
            'alerts.deliver' => new CanonicalOperationType('alerts.deliver', 'platform_foundation', null, 'Alerts delivery', false, 120),
            'baseline.capture' => new CanonicalOperationType('baseline.capture', 'platform_foundation', 'baseline_snapshot', 'Baseline capture', true, 120),
            'baseline.compare' => new CanonicalOperationType('baseline.compare', 'platform_foundation', null, 'Baseline compare', true, 120),
            'permission.posture.check' => new CanonicalOperationType('permission.posture.check', 'platform_foundation', null, 'Permission posture check', false, 30),
            'entra.admin_roles.scan' => new CanonicalOperationType('entra.admin_roles.scan', 'entra', null, 'Entra admin roles scan', false, 60),
            'environment.review_pack.generate' => new CanonicalOperationType('environment.review_pack.generate', 'platform_foundation', 'review_pack', 'Review pack generation', true, 60),
            'environment.review.compose' => new CanonicalOperationType('environment.review.compose', 'platform_foundation', 'environment_review', 'Review composition', true, 60),
            'tenant.evidence.snapshot.generate' => new CanonicalOperationType('tenant.evidence.snapshot.generate', 'platform_foundation', 'evidence_snapshot', 'Evidence snapshot generation', true, 120),
            'rbac.health_check' => new CanonicalOperationType('rbac.health_check', 'intune', null, 'RBAC health check', false, 30),
        ];
    }

    /**
     * @return list<OperationTypeAlias>
     */
    private static function operationAliases(): array
    {
        return [
            new OperationTypeAlias('policy.sync', 'policy.sync', 'canonical', true),
            new OperationTypeAlias('policy.sync_one', 'policy.sync', 'legacy_alias', false, 'Legacy single-policy sync values resolve to the canonical policy.sync operation.', 'Prefer policy.sync on platform-owned read paths.'),
            new OperationTypeAlias('policy.snapshot', 'policy.snapshot', 'canonical', true),
            new OperationTypeAlias('policy.capture_snapshot', 'policy.snapshot', 'canonical', true),
            new OperationTypeAlias('policy.delete', 'policy.delete', 'canonical', true),
            new OperationTypeAlias('policy.restore', 'policy.restore', 'canonical', true),
            new OperationTypeAlias('policy.unignore', 'policy.restore', 'legacy_alias', false, 'Legacy policy.unignore values resolve to policy.restore for operator-facing wording.', 'Prefer policy.restore on new platform-owned read models.'),
            new OperationTypeAlias('policy.export', 'policy.export', 'canonical', true),
            new OperationTypeAlias('provider.connection.check', 'provider.connection.check', 'canonical', true),
            new OperationTypeAlias('inventory.sync', 'inventory.sync', 'canonical', true),
            new OperationTypeAlias('inventory_sync', 'inventory.sync', 'legacy_alias', false, 'Legacy inventory_sync storage values resolve to the canonical inventory.sync operation.', 'Preserve stored values during rollout while showing inventory.sync semantics on read paths.'),
            new OperationTypeAlias('provider.inventory.sync', 'inventory.sync', 'legacy_alias', false, 'Provider-prefixed historical inventory sync values share the same operator meaning as inventory sync.', 'Avoid emitting provider.inventory.sync on new platform-owned surfaces.'),
            new OperationTypeAlias('compliance.snapshot', 'compliance.snapshot', 'canonical', true),
            new OperationTypeAlias('provider.compliance.snapshot', 'compliance.snapshot', 'legacy_alias', false, 'Provider-prefixed compliance snapshot values resolve to the canonical compliance.snapshot operation.', 'Avoid emitting provider.compliance.snapshot on new platform-owned surfaces.'),
            new OperationTypeAlias('directory.groups.sync', 'directory.groups.sync', 'canonical', true),
            new OperationTypeAlias('entra_group_sync', 'directory.groups.sync', 'legacy_alias', false, 'Historical entra_group_sync values resolve to directory.groups.sync.', 'Prefer directory.groups.sync on new platform-owned read models.'),
            new OperationTypeAlias('backup_set.update', 'backup_set.update', 'canonical', true),
            new OperationTypeAlias('backup_set.archive', 'backup_set.archive', 'canonical', true),
            new OperationTypeAlias('backup_set.delete', 'backup_set.archive', 'canonical', true),
            new OperationTypeAlias('backup_set.restore', 'backup_set.restore', 'canonical', true),
            new OperationTypeAlias('backup_set.force_delete', 'backup_set.delete', 'legacy_alias', false, 'Force-delete wording is normalized to the canonical delete label.', 'Use backup_set.delete for new platform-owned summaries.'),
            new OperationTypeAlias('backup.schedule.execute', 'backup.schedule.execute', 'canonical', true),
            new OperationTypeAlias('backup_schedule_run', 'backup.schedule.execute', 'legacy_alias', false, 'Historical backup_schedule_run values resolve to backup.schedule.execute.', 'Prefer backup.schedule.execute on canonical read paths.'),
            new OperationTypeAlias('backup.schedule.retention', 'backup.schedule.retention', 'canonical', true),
            new OperationTypeAlias('backup_schedule_retention', 'backup.schedule.retention', 'legacy_alias', false, 'Legacy backup schedule retention values resolve to backup.schedule.retention.', 'Prefer dotted canonical backup schedule naming on new read paths.'),
            new OperationTypeAlias('backup.schedule.purge', 'backup.schedule.purge', 'canonical', true),
            new OperationTypeAlias('backup_schedule_purge', 'backup.schedule.purge', 'legacy_alias', false, 'Legacy backup schedule purge values resolve to backup.schedule.purge.', 'Prefer dotted canonical backup schedule naming on new read paths.'),
            new OperationTypeAlias('restore.execute', 'restore.execute', 'canonical', true),
            new OperationTypeAlias('promotion.execute', 'promotion.execute', 'canonical', true),
            new OperationTypeAlias('assignments.fetch', 'assignments.fetch', 'canonical', true),
            new OperationTypeAlias('assignments.restore', 'assignments.restore', 'canonical', true),
            new OperationTypeAlias('ops.reconcile_adapter_runs', 'ops.reconcile_adapter_runs', 'canonical', true),
            new OperationTypeAlias('directory.role_definitions.sync', 'directory.role_definitions.sync', 'canonical', true),
            new OperationTypeAlias('directory_role_definitions.sync', 'directory.role_definitions.sync', 'legacy_alias', false, 'Legacy directory_role_definitions.sync values resolve to directory.role_definitions.sync.', 'Prefer dotted role-definition naming on new read paths.'),
            new OperationTypeAlias('restore_run.delete', 'restore_run.delete', 'canonical', true),
            new OperationTypeAlias('restore_run.restore', 'restore_run.restore', 'canonical', true),
            new OperationTypeAlias('restore_run.force_delete', 'restore_run.force_delete', 'canonical', true),
            new OperationTypeAlias('tenant.sync', 'tenant.sync', 'canonical', true),
            new OperationTypeAlias('policy_version.prune', 'policy_version.prune', 'canonical', true),
            new OperationTypeAlias('policy_version.restore', 'policy_version.restore', 'canonical', true),
            new OperationTypeAlias('policy_version.force_delete', 'policy_version.force_delete', 'canonical', true),
            new OperationTypeAlias('alerts.evaluate', 'alerts.evaluate', 'canonical', true),
            new OperationTypeAlias('alerts.deliver', 'alerts.deliver', 'canonical', true),
            new OperationTypeAlias('baseline.capture', 'baseline.capture', 'canonical', true),
            new OperationTypeAlias('baseline.compare', 'baseline.compare', 'canonical', true),
            new OperationTypeAlias('baseline_capture', 'baseline.capture', 'legacy_alias', false, 'Historical baseline_capture values resolve to baseline.capture.', 'Prefer baseline.capture on canonical read paths.'),
            new OperationTypeAlias('baseline_compare', 'baseline.compare', 'legacy_alias', false, 'Historical baseline_compare values resolve to baseline.compare.', 'Prefer baseline.compare on canonical read paths.'),
            new OperationTypeAlias('permission.posture.check', 'permission.posture.check', 'canonical', true),
            new OperationTypeAlias('permission_posture_check', 'permission.posture.check', 'legacy_alias', false, 'Historical permission_posture_check values resolve to permission.posture.check.', 'Prefer dotted permission posture naming on new read paths.'),
            new OperationTypeAlias('entra.admin_roles.scan', 'entra.admin_roles.scan', 'canonical', true),
            new OperationTypeAlias('environment.review_pack.generate', 'environment.review_pack.generate', 'canonical', true),
            new OperationTypeAlias('environment.review.compose', 'environment.review.compose', 'canonical', true),
            new OperationTypeAlias('tenant.evidence.snapshot.generate', 'tenant.evidence.snapshot.generate', 'canonical', true),
            new OperationTypeAlias('rbac.health_check', 'rbac.health_check', 'canonical', true),
        ];
    }
}
