<?php

declare(strict_types=1);

namespace App\Support\Governance\Controls;

use InvalidArgumentException;

final readonly class CanonicalControlDefinition
{
    /**
     * @param  list<EvidenceArchetype>  $evidenceArchetypes
     */
    public function __construct(
        public string $controlKey,
        public string $name,
        public string $domainKey,
        public string $subdomainKey,
        public string $controlClass,
        public string $summary,
        public string $operatorDescription,
        public DetectabilityClass $detectabilityClass,
        public EvaluationStrategy $evaluationStrategy,
        public array $evidenceArchetypes,
        public ArtifactSuitability $artifactSuitability,
        public string $historicalStatus = 'active',
    ) {
        foreach ([
            'control key' => $this->controlKey,
            'name' => $this->name,
            'domain key' => $this->domainKey,
            'subdomain key' => $this->subdomainKey,
            'control class' => $this->controlClass,
            'summary' => $this->summary,
            'operator description' => $this->operatorDescription,
            'historical status' => $this->historicalStatus,
        ] as $label => $value) {
            if (trim($value) === '') {
                throw new InvalidArgumentException(sprintf('Canonical control definitions require a non-empty %s.', $label));
            }
        }

        if ($this->controlKey !== mb_strtolower($this->controlKey) || preg_match('/^[a-z][a-z0-9_]*$/', $this->controlKey) !== 1) {
            throw new InvalidArgumentException(sprintf('Canonical control key [%s] must be a lowercase provider-neutral slug.', $this->controlKey));
        }

        if (! in_array($this->historicalStatus, ['active', 'retired'], true)) {
            throw new InvalidArgumentException(sprintf('Canonical control [%s] has an unsupported historical status.', $this->controlKey));
        }

        if ($this->evidenceArchetypes === []) {
            throw new InvalidArgumentException(sprintf('Canonical control [%s] must declare at least one evidence archetype.', $this->controlKey));
        }
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public static function fromArray(array $data): self
    {
        return new self(
            controlKey: (string) ($data['control_key'] ?? ''),
            name: (string) ($data['name'] ?? ''),
            domainKey: (string) ($data['domain_key'] ?? ''),
            subdomainKey: (string) ($data['subdomain_key'] ?? ''),
            controlClass: (string) ($data['control_class'] ?? ''),
            summary: (string) ($data['summary'] ?? ''),
            operatorDescription: (string) ($data['operator_description'] ?? ''),
            detectabilityClass: DetectabilityClass::from((string) ($data['detectability_class'] ?? '')),
            evaluationStrategy: EvaluationStrategy::from((string) ($data['evaluation_strategy'] ?? '')),
            evidenceArchetypes: self::evidenceArchetypes($data['evidence_archetypes'] ?? []),
            artifactSuitability: ArtifactSuitability::fromArray(is_array($data['artifact_suitability'] ?? null) ? $data['artifact_suitability'] : []),
            historicalStatus: (string) ($data['historical_status'] ?? 'active'),
        );
    }

    /**
     * @return array{
     *     control_key: string,
     *     name: string,
     *     domain_key: string,
     *     subdomain_key: string,
     *     control_class: string,
     *     summary: string,
     *     operator_description: string,
     *     detectability_class: string,
     *     evaluation_strategy: string,
     *     evidence_archetypes: list<string>,
     *     artifact_suitability: array{baseline: bool, drift: bool, finding: bool, exception: bool, evidence: bool, review: bool, report: bool},
     *     historical_status: string
     * }
     */
    public function toArray(): array
    {
        return [
            'control_key' => $this->controlKey,
            'name' => $this->name,
            'domain_key' => $this->domainKey,
            'subdomain_key' => $this->subdomainKey,
            'control_class' => $this->controlClass,
            'summary' => $this->summary,
            'operator_description' => $this->operatorDescription,
            'detectability_class' => $this->detectabilityClass->value,
            'evaluation_strategy' => $this->evaluationStrategy->value,
            'evidence_archetypes' => array_map(
                static fn (EvidenceArchetype $archetype): string => $archetype->value,
                $this->evidenceArchetypes,
            ),
            'artifact_suitability' => $this->artifactSuitability->toArray(),
            'historical_status' => $this->historicalStatus,
        ];
    }

    public function isRetired(): bool
    {
        return $this->historicalStatus === 'retired';
    }

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