<?php

declare(strict_types=1);

namespace App\Support\ReasonTranslation;

use App\Support\Ui\OperatorExplanation\TrustworthinessLevel;
use InvalidArgumentException;

final readonly class ReasonResolutionEnvelope
{
    /**
     * @param  array<int, NextStepOption>  $nextSteps
     */
    public function __construct(
        public string $internalCode,
        public string $operatorLabel,
        public string $shortExplanation,
        public string $actionability,
        public array $nextSteps = [],
        public bool $showNoActionNeeded = false,
        public ?string $diagnosticCodeLabel = null,
        public string $trustImpact = TrustworthinessLevel::LimitedConfidence->value,
        public ?string $absencePattern = null,
        public ?ReasonOwnershipDescriptor $reasonOwnership = null,
    ) {
        if (trim($this->internalCode) === '') {
            throw new InvalidArgumentException('Reason envelopes must preserve an internal code.');
        }

        if (trim($this->operatorLabel) === '') {
            throw new InvalidArgumentException('Reason envelopes require an operator label.');
        }

        if (trim($this->shortExplanation) === '') {
            throw new InvalidArgumentException('Reason envelopes require a short explanation.');
        }

        if (! in_array($this->actionability, [
            'retryable_transient',
            'permanent_configuration',
            'prerequisite_missing',
            'non_actionable',
        ], true)) {
            throw new InvalidArgumentException('Unsupported reason actionability: '.$this->actionability);
        }

        if (! in_array($this->trustImpact, array_map(
            static fn (TrustworthinessLevel $level): string => $level->value,
            TrustworthinessLevel::cases(),
        ), true)) {
            throw new InvalidArgumentException('Unsupported reason trust impact: '.$this->trustImpact);
        }

        if ($this->absencePattern !== null && ! in_array($this->absencePattern, [
            'none',
            'true_no_result',
            'missing_input',
            'blocked_prerequisite',
            'suppressed_output',
            'unavailable',
        ], true)) {
            throw new InvalidArgumentException('Unsupported reason absence pattern: '.$this->absencePattern);
        }

        foreach ($this->nextSteps as $nextStep) {
            if (! $nextStep instanceof NextStepOption) {
                throw new InvalidArgumentException('Reason envelopes only support NextStepOption instances.');
            }
        }
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public static function fromArray(array $data): ?self
    {
        $internalCode = is_string($data['internal_code'] ?? null)
            ? trim((string) $data['internal_code'])
            : (is_string($data['internalCode'] ?? null) ? trim((string) $data['internalCode']) : '');
        $operatorLabel = is_string($data['operator_label'] ?? null)
            ? trim((string) $data['operator_label'])
            : (is_string($data['operatorLabel'] ?? null) ? trim((string) $data['operatorLabel']) : '');
        $shortExplanation = is_string($data['short_explanation'] ?? null)
            ? trim((string) $data['short_explanation'])
            : (is_string($data['shortExplanation'] ?? null) ? trim((string) $data['shortExplanation']) : '');
        $actionability = is_string($data['actionability'] ?? null) ? trim((string) $data['actionability']) : '';
        $nextSteps = is_array($data['next_steps'] ?? null)
            ? NextStepOption::collect($data['next_steps'])
            : (is_array($data['nextSteps'] ?? null) ? NextStepOption::collect($data['nextSteps']) : []);
        $showNoActionNeeded = (bool) ($data['show_no_action_needed'] ?? $data['showNoActionNeeded'] ?? false);
        $diagnosticCodeLabel = is_string($data['diagnostic_code_label'] ?? null)
            ? trim((string) $data['diagnostic_code_label'])
            : (is_string($data['diagnosticCodeLabel'] ?? null) ? trim((string) $data['diagnosticCodeLabel']) : null);
        $trustImpact = is_string($data['trust_impact'] ?? null)
            ? trim((string) $data['trust_impact'])
            : (is_string($data['trustImpact'] ?? null) ? trim((string) $data['trustImpact']) : TrustworthinessLevel::LimitedConfidence->value);
        $absencePattern = is_string($data['absence_pattern'] ?? null)
            ? trim((string) $data['absence_pattern'])
            : (is_string($data['absencePattern'] ?? null) ? trim((string) $data['absencePattern']) : null);
        $reasonOwnership = is_array($data['reason_owner'] ?? null)
            ? ReasonOwnershipDescriptor::fromArray($data['reason_owner'])
            : (is_array($data['reasonOwnership'] ?? null) ? ReasonOwnershipDescriptor::fromArray($data['reasonOwnership']) : ReasonOwnershipDescriptor::fromArray([
                'owner_layer' => $data['owner_layer'] ?? $data['ownerLayer'] ?? null,
                'owner_namespace' => $data['owner_namespace'] ?? $data['ownerNamespace'] ?? null,
                'reason_code' => $data['reason_code'] ?? $internalCode,
                'platform_reason_family' => $data['platform_reason_family'] ?? $data['platformReasonFamily'] ?? null,
            ]));

        if ($internalCode === '' || $operatorLabel === '' || $shortExplanation === '' || $actionability === '') {
            return null;
        }

        return new self(
            internalCode: $internalCode,
            operatorLabel: $operatorLabel,
            shortExplanation: $shortExplanation,
            actionability: $actionability,
            nextSteps: $nextSteps,
            showNoActionNeeded: $showNoActionNeeded,
            diagnosticCodeLabel: $diagnosticCodeLabel !== '' ? $diagnosticCodeLabel : null,
            trustImpact: $trustImpact !== '' ? $trustImpact : TrustworthinessLevel::LimitedConfidence->value,
            absencePattern: $absencePattern !== '' ? $absencePattern : null,
            reasonOwnership: $reasonOwnership,
        );
    }

    /**
     * @param  array<int, NextStepOption>  $nextSteps
     */
    public function withNextSteps(array $nextSteps): self
    {
        return new self(
            internalCode: $this->internalCode,
            operatorLabel: $this->operatorLabel,
            shortExplanation: $this->shortExplanation,
            actionability: $this->actionability,
            nextSteps: $nextSteps,
            showNoActionNeeded: $this->showNoActionNeeded,
            diagnosticCodeLabel: $this->diagnosticCodeLabel,
            trustImpact: $this->trustImpact,
            absencePattern: $this->absencePattern,
            reasonOwnership: $this->reasonOwnership,
        );
    }

    public function withReasonOwnership(?ReasonOwnershipDescriptor $reasonOwnership): self
    {
        return new self(
            internalCode: $this->internalCode,
            operatorLabel: $this->operatorLabel,
            shortExplanation: $this->shortExplanation,
            actionability: $this->actionability,
            nextSteps: $this->nextSteps,
            showNoActionNeeded: $this->showNoActionNeeded,
            diagnosticCodeLabel: $this->diagnosticCodeLabel,
            trustImpact: $this->trustImpact,
            absencePattern: $this->absencePattern,
            reasonOwnership: $reasonOwnership,
        );
    }

    public function firstNextStep(): ?NextStepOption
    {
        return $this->nextSteps[0] ?? null;
    }

    public function guidanceText(): ?string
    {
        $nextStep = $this->firstNextStep();

        if ($nextStep instanceof NextStepOption) {
            return 'Next step: '.rtrim($nextStep->label, ". \t\n\r\0\x0B").'.';
        }

        if ($this->showNoActionNeeded) {
            return 'No action needed.';
        }

        return null;
    }

    /**
     * @return array<int, string>
     */
    public function toBodyLines(bool $includeGuidance = true): array
    {
        $lines = [
            $this->operatorLabel,
            $this->shortExplanation,
        ];

        if ($includeGuidance) {
            $guidance = $this->guidanceText();

            if (is_string($guidance) && $guidance !== '') {
                $lines[] = $guidance;
            }
        }

        return array_values(array_filter($lines, static fn (?string $line): bool => is_string($line) && trim($line) !== ''));
    }

    public function diagnosticCode(): string
    {
        return $this->diagnosticCodeLabel !== null && trim($this->diagnosticCodeLabel) !== ''
            ? $this->diagnosticCodeLabel
            : $this->internalCode;
    }

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

    public function ownerNamespace(): ?string
    {
        return $this->reasonOwnership?->ownerNamespace;
    }

    public function platformReasonFamily(): ?string
    {
        return $this->reasonOwnership?->platformReasonFamily->value;
    }

    public function platformReasonFamilyEnum(): ?PlatformReasonFamily
    {
        return $this->reasonOwnership?->platformReasonFamily;
    }

    /**
     * @return array<int, array{label: string, url: string}>
     */
    public function toLegacyNextSteps(): array
    {
        return array_values(array_map(
            static fn (NextStepOption $nextStep): array => $nextStep->toLegacyArray(),
            array_filter(
                $this->nextSteps,
                static fn (NextStepOption $nextStep): bool => $nextStep->kind === 'link' && $nextStep->destination !== null,
            ),
        ));
    }

    /**
     * @return array{
     *     internal_code: string,
     *     operator_label: string,
     *     short_explanation: string,
     *     actionability: string,
     *     next_steps: array<int, array{
     *         label: string,
     *         kind: string,
     *         destination: ?string,
     *         authorization_required: bool,
     *         scope: string
     *     }>,
     *     show_no_action_needed: bool,
     *     diagnostic_code_label: string,
     *     trust_impact: string,
     *     absence_pattern: ?string,
     *     reason_owner: ?array{
     *         owner_layer: string,
     *         owner_namespace: string,
     *         reason_code: string,
     *         platform_reason_family: string
     *     },
     *     owner_layer: ?string,
     *     owner_namespace: ?string,
     *     platform_reason_family: ?string
     * }
     */
    public function toArray(): array
    {
        return [
            'internal_code' => $this->internalCode,
            'operator_label' => $this->operatorLabel,
            'short_explanation' => $this->shortExplanation,
            'actionability' => $this->actionability,
            'next_steps' => array_map(
                static fn (NextStepOption $nextStep): array => $nextStep->toArray(),
                $this->nextSteps,
            ),
            'show_no_action_needed' => $this->showNoActionNeeded,
            'diagnostic_code_label' => $this->diagnosticCode(),
            'trust_impact' => $this->trustImpact,
            'absence_pattern' => $this->absencePattern,
            'reason_owner' => $this->reasonOwnership?->toArray(),
            'owner_layer' => $this->ownerLayer(),
            'owner_namespace' => $this->ownerNamespace(),
            'platform_reason_family' => $this->platformReasonFamily(),
        ];
    }
}
