<?php

declare(strict_types=1);

namespace App\Support\Baselines;

use App\Support\Inventory\InventoryPolicyTypeMeta;

/**
 * Value object for baseline scope resolution.
 *
 * A scope defines which policy types are included in a baseline profile.
 *
 * Spec 116 semantics:
 * - Empty policy_types means "all supported policy types" (excluding foundations).
 * - Empty foundation_types means "none".
 */
final class BaselineScope
{
    /**
     * @param  array<string>  $policyTypes
     * @param  array<string>  $foundationTypes
     */
    public function __construct(
        public readonly array $policyTypes = [],
        public readonly array $foundationTypes = [],
    ) {}

    /**
     * Create from the scope_jsonb column value.
     *
     * @param  array<string, mixed>|null  $scopeJsonb
     */
    public static function fromJsonb(?array $scopeJsonb): self
    {
        if ($scopeJsonb === null) {
            return new self;
        }

        $policyTypes = $scopeJsonb['policy_types'] ?? [];
        $foundationTypes = $scopeJsonb['foundation_types'] ?? [];

        $policyTypes = is_array($policyTypes) ? array_values(array_filter($policyTypes, 'is_string')) : [];
        $foundationTypes = is_array($foundationTypes) ? array_values(array_filter($foundationTypes, 'is_string')) : [];

        return new self(
            policyTypes: $policyTypes === [] ? [] : self::normalizePolicyTypes($policyTypes),
            foundationTypes: self::normalizeFoundationTypes($foundationTypes),
        );
    }

    /**
     * Normalize the effective scope by intersecting profile scope with an optional override.
     *
     * Override can only narrow the profile scope (subset enforcement).
     * Empty override means "no override".
     */
    public static function effective(self $profileScope, ?self $overrideScope): self
    {
        $profileScope = $profileScope->expandDefaults();

        if ($overrideScope === null || $overrideScope->isEmpty()) {
            return $profileScope;
        }

        $overridePolicyTypes = self::normalizePolicyTypes($overrideScope->policyTypes);
        $overrideFoundationTypes = self::normalizeFoundationTypes($overrideScope->foundationTypes);

        $effectivePolicyTypes = $overridePolicyTypes !== []
            ? array_values(array_intersect($profileScope->policyTypes, $overridePolicyTypes))
            : $profileScope->policyTypes;

        $effectiveFoundationTypes = $overrideFoundationTypes !== []
            ? array_values(array_intersect($profileScope->foundationTypes, $overrideFoundationTypes))
            : $profileScope->foundationTypes;

        return new self(
            policyTypes: self::uniqueSorted($effectivePolicyTypes),
            foundationTypes: self::uniqueSorted($effectiveFoundationTypes),
        );
    }

    /**
     * An empty scope means "no override" (for override_scope semantics).
     */
    public function isEmpty(): bool
    {
        return $this->policyTypes === [] && $this->foundationTypes === [];
    }

    /**
     * Apply Spec 116 defaults and filter to supported types.
     */
    public function expandDefaults(): self
    {
        $policyTypes = $this->policyTypes === []
            ? self::supportedPolicyTypes()
            : self::normalizePolicyTypes($this->policyTypes);

        $foundationTypes = self::normalizeFoundationTypes($this->foundationTypes);

        return new self(
            policyTypes: $policyTypes,
            foundationTypes: $foundationTypes,
        );
    }

    /**
     * @return list<string>
     */
    public function allTypes(): array
    {
        $expanded = $this->expandDefaults();

        return self::uniqueSorted(array_merge(
            $expanded->policyTypes,
            $expanded->foundationTypes,
        ));
    }

    /**
     * @return array<string, mixed>
     */
    public function toJsonb(): array
    {
        return [
            'policy_types' => $this->policyTypes,
            'foundation_types' => $this->foundationTypes,
        ];
    }

    /**
     * Effective scope payload for OperationRun.context.
     *
     * @return array{policy_types: list<string>, foundation_types: list<string>, all_types: list<string>, foundations_included: bool}
     */
    public function toEffectiveScopeContext(): array
    {
        $expanded = $this->expandDefaults();
        $allTypes = self::uniqueSorted(array_merge($expanded->policyTypes, $expanded->foundationTypes));

        return [
            'policy_types' => $expanded->policyTypes,
            'foundation_types' => $expanded->foundationTypes,
            'all_types' => $allTypes,
            'foundations_included' => $expanded->foundationTypes !== [],
        ];
    }

    /**
     * @return list<string>
     */
    private static function supportedPolicyTypes(): array
    {
        $supported = config('tenantpilot.supported_policy_types', []);

        if (! is_array($supported)) {
            return [];
        }

        $types = collect($supported)
            ->filter(fn (mixed $row): bool => is_array($row) && filled($row['type'] ?? null))
            ->map(fn (array $row): string => (string) $row['type'])
            ->filter(fn (string $type): bool => $type !== '')
            ->values()
            ->all();

        return self::uniqueSorted($types);
    }

    /**
     * @return list<string>
     */
    private static function supportedFoundationTypes(): array
    {
        $types = collect(InventoryPolicyTypeMeta::baselineSupportedFoundations())
            ->filter(fn (mixed $row): bool => is_array($row) && filled($row['type'] ?? null))
            ->map(fn (array $row): string => (string) $row['type'])
            ->filter(fn (string $type): bool => $type !== '')
            ->values()
            ->all();

        return self::uniqueSorted($types);
    }

    /**
     * @param  array<string>  $types
     * @return list<string>
     */
    private static function normalizePolicyTypes(array $types): array
    {
        $supported = self::supportedPolicyTypes();

        return self::uniqueSorted(array_values(array_intersect($types, $supported)));
    }

    /**
     * @param  array<string>  $types
     * @return list<string>
     */
    private static function normalizeFoundationTypes(array $types): array
    {
        $supported = self::supportedFoundationTypes();

        return self::uniqueSorted(array_values(array_intersect($types, $supported)));
    }

    /**
     * @param  array<int, string>  $types
     * @return list<string>
     */
    private static function uniqueSorted(array $types): array
    {
        $types = array_values(array_unique(array_filter($types, fn (mixed $type): bool => is_string($type) && $type !== '')));

        sort($types, SORT_STRING);

        return $types;
    }
}
