<?php

declare(strict_types=1);

namespace App\Support\Artifacts;

use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;

/**
 * @implements Arrayable<string, mixed>
 */
final readonly class ArtifactSourceDescriptor implements Arrayable, JsonSerializable
{
    public function __construct(
        public int $workspaceId,
        public int $tenantId,
        public int $managedEnvironmentId,
        public string $sourceFamily,
        public string $sourceKind,
        public string $providerKey,
        public ?int $providerConnectionId,
        public string $sourceTargetKind,
        public ?string $sourceTargetIdentifier,
        public ?string $detectorKey,
        public ?string $controlKey,
        public ?int $packageRunId = null,
    ) {}

    /**
     * @param  array<string, mixed>  $payload
     */
    public static function fromArray(array $payload): self
    {
        return new self(
            workspaceId: max(0, (int) ($payload['workspace_id'] ?? 0)),
            tenantId: max(0, (int) ($payload['tenant_id'] ?? $payload['managed_environment_id'] ?? 0)),
            managedEnvironmentId: max(0, (int) ($payload['managed_environment_id'] ?? $payload['tenant_id'] ?? 0)),
            sourceFamily: self::stringOrDefault($payload['source_family'] ?? null, ArtifactSourceTaxonomy::SOURCE_FAMILY_EVIDENCE_SNAPSHOT),
            sourceKind: self::stringOrDefault($payload['source_kind'] ?? null, ArtifactSourceTaxonomy::SOURCE_KIND_MODEL_SUMMARY),
            providerKey: self::stringOrDefault($payload['provider_key'] ?? null, 'microsoft'),
            providerConnectionId: self::nullableInt($payload['provider_connection_id'] ?? null),
            sourceTargetKind: self::stringOrDefault($payload['source_target_kind'] ?? null, ArtifactSourceTaxonomy::SOURCE_TARGET_MANAGED_ENVIRONMENT),
            sourceTargetIdentifier: self::nullableString($payload['source_target_identifier'] ?? null),
            detectorKey: self::nullableString($payload['detector_key'] ?? null),
            controlKey: self::nullableString($payload['control_key'] ?? null),
            packageRunId: self::nullableInt($payload['package_run_id'] ?? null),
        );
    }

    /**
     * @return array{
     *     workspace_id: int,
     *     tenant_id: int,
     *     managed_environment_id: int,
     *     source_family: string,
     *     source_kind: string,
     *     provider_key: string,
     *     provider_connection_id: ?int,
     *     source_target_kind: string,
     *     source_target_identifier: ?string,
     *     detector_key: ?string,
     *     control_key: ?string,
     *     package_run_id: ?int
     * }
     */
    public function toArray(): array
    {
        return [
            'workspace_id' => $this->workspaceId,
            'tenant_id' => $this->tenantId,
            'managed_environment_id' => $this->managedEnvironmentId,
            'source_family' => $this->sourceFamily,
            'source_kind' => $this->sourceKind,
            'provider_key' => $this->providerKey,
            'provider_connection_id' => $this->providerConnectionId,
            'source_target_kind' => $this->sourceTargetKind,
            'source_target_identifier' => $this->sourceTargetIdentifier,
            'detector_key' => $this->detectorKey,
            'control_key' => $this->controlKey,
            'package_run_id' => $this->packageRunId,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

    private static function stringOrDefault(mixed $value, string $default): string
    {
        if (! is_string($value) || trim($value) === '') {
            return $default;
        }

        return trim($value);
    }

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

        $value = trim($value);

        return $value === '' ? null : $value;
    }

    private static function nullableInt(mixed $value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

        return is_numeric($value) ? (int) $value : null;
    }
}
