<?php

declare(strict_types=1);

namespace App\Support\Governance;

use InvalidArgumentException;

final readonly class PlatformVocabularyTerm
{
    /**
     * @param  list<string>  $allowedContexts
     * @param  list<string>  $legacyAliases
     * @param  list<string>  $forbiddenPlatformAliases
     */
    public function __construct(
        public string $termKey,
        public string $canonicalLabel,
        public string $canonicalDescription,
        public string $boundaryClassification,
        public string $ownerLayer,
        public array $allowedContexts = [],
        public array $legacyAliases = [],
        public ?string $aliasRetirementPath = null,
        public array $forbiddenPlatformAliases = [],
    ) {
        if (trim($this->termKey) === '' || trim($this->canonicalLabel) === '' || trim($this->canonicalDescription) === '') {
            throw new InvalidArgumentException('Platform vocabulary terms require a key, label, and description.');
        }

        if ($this->legacyAliases !== [] && blank($this->aliasRetirementPath)) {
            throw new InvalidArgumentException('Platform vocabulary terms with legacy aliases must declare an alias retirement path.');
        }
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public static function fromArray(array $data): self
    {
        return new self(
            termKey: (string) ($data['term_key'] ?? ''),
            canonicalLabel: (string) ($data['canonical_label'] ?? ''),
            canonicalDescription: (string) ($data['canonical_description'] ?? ''),
            boundaryClassification: (string) ($data['boundary_classification'] ?? ''),
            ownerLayer: (string) ($data['owner_layer'] ?? ''),
            allowedContexts: self::stringList($data['allowed_contexts'] ?? []),
            legacyAliases: self::stringList($data['legacy_aliases'] ?? []),
            aliasRetirementPath: is_string($data['alias_retirement_path'] ?? null)
                ? trim((string) $data['alias_retirement_path'])
                : null,
            forbiddenPlatformAliases: self::stringList($data['forbidden_platform_aliases'] ?? []),
        );
    }

    public function matches(string $term): bool
    {
        $normalized = trim(mb_strtolower($term));

        if ($normalized === '') {
            return false;
        }

        return in_array($normalized, array_map(
            static fn (string $candidate): string => trim(mb_strtolower($candidate)),
            array_merge([$this->termKey], $this->legacyAliases),
        ), true);
    }

    /**
     * @return 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 toArray(): array
    {
        return [
            'term_key' => $this->termKey,
            'canonical_label' => $this->canonicalLabel,
            'canonical_description' => $this->canonicalDescription,
            'boundary_classification' => $this->boundaryClassification,
            'owner_layer' => $this->ownerLayer,
            'allowed_contexts' => $this->allowedContexts,
            'legacy_aliases' => $this->legacyAliases,
            'alias_retirement_path' => $this->aliasRetirementPath,
            'forbidden_platform_aliases' => $this->forbiddenPlatformAliases,
        ];
    }

    /**
     * @param  iterable<mixed>  $values
     * @return list<string>
     */
    private static function stringList(iterable $values): array
    {
        return collect($values)
            ->filter(static fn (mixed $value): bool => is_string($value) && trim($value) !== '')
            ->map(static fn (string $value): string => trim($value))
            ->values()
            ->all();
    }
}