<?php

declare(strict_types=1);

namespace App\Support\Inventory;

final readonly class InventoryCoverage
{
    public const string StatusSucceeded = 'succeeded';

    public const string StatusFailed = 'failed';

    public const string StatusSkipped = 'skipped';

    /**
     * @param  array<string, array{status: string, item_count?: int, error_code?: string|null}>  $policyTypes
     * @param  array<string, array{status: string, item_count?: int, error_code?: string|null}>  $foundationTypes
     */
    public function __construct(
        public array $policyTypes,
        public array $foundationTypes,
    ) {}

    public static function fromContext(mixed $context): ?self
    {
        if (! is_array($context)) {
            return null;
        }

        $inventory = $context['inventory'] ?? null;
        if (! is_array($inventory)) {
            return null;
        }

        $coverage = $inventory['coverage'] ?? null;
        if (! is_array($coverage)) {
            return null;
        }

        $policyTypes = self::normalizeCoverageMap($coverage['policy_types'] ?? null);
        $foundationTypes = self::normalizeCoverageMap($coverage['foundation_types'] ?? null);

        if ($policyTypes === [] && $foundationTypes === []) {
            return null;
        }

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

    /**
     * @return list<string>
     */
    public function coveredTypes(): array
    {
        $covered = [];

        foreach (array_merge($this->policyTypes, $this->foundationTypes) as $type => $meta) {
            if (($meta['status'] ?? null) === self::StatusSucceeded) {
                $covered[] = $type;
            }
        }

        sort($covered, SORT_STRING);

        return array_values(array_unique($covered));
    }

    /**
     * @return array<string, array{
     *     segment: 'policy'|'foundation',
     *     type: string,
     *     status: string,
     *     item_count?: int,
     *     error_code?: string|null
     * }>
     */
    public function rows(): array
    {
        $rows = [];

        foreach ($this->policyTypes as $type => $meta) {
            $rows[$type] = array_merge($meta, [
                'segment' => 'policy',
                'type' => $type,
            ]);
        }

        foreach ($this->foundationTypes as $type => $meta) {
            $rows[$type] = array_merge($meta, [
                'segment' => 'foundation',
                'type' => $type,
            ]);
        }

        ksort($rows);

        return $rows;
    }

    /**
     * @return array{
     *     segment: 'policy'|'foundation',
     *     type: string,
     *     status: string,
     *     item_count?: int,
     *     error_code?: string|null
     * }|null
     */
    public function row(string $type): ?array
    {
        return $this->rows()[$type] ?? null;
    }

    /**
     * Build the canonical `inventory.coverage.*` payload for OperationRun.context.
     *
     * @param  array<string, string|array{status: string, item_count?: int, error_code?: string|null}>  $statusByType
     * @param  list<string>  $foundationTypes
     * @return array{policy_types: array<string, array{status: string}>, foundation_types: array<string, array{status: string}>}
     */
    public static function buildPayload(array $statusByType, array $foundationTypes): array
    {
        $foundationTypes = array_values(array_unique(array_filter($foundationTypes, fn (mixed $type): bool => is_string($type) && $type !== '')));
        $foundationLookup = array_fill_keys($foundationTypes, true);

        $policy = [];
        $foundations = [];

        foreach ($statusByType as $type => $status) {
            if (! is_string($type) || $type === '') {
                continue;
            }

            $row = self::normalizeBuildRow($status);

            if ($row === null) {
                continue;
            }

            if (array_key_exists($type, $foundationLookup)) {
                $foundations[$type] = $row;

                continue;
            }

            $policy[$type] = $row;
        }

        ksort($policy);
        ksort($foundations);

        return [
            'policy_types' => $policy,
            'foundation_types' => $foundations,
        ];
    }

    /**
     * @return array{status: string, item_count?: int, error_code?: string|null}|null
     */
    private static function normalizeBuildRow(mixed $value): ?array
    {
        if (is_string($value)) {
            $status = self::normalizeStatus($value);

            return $status === null ? null : ['status' => $status];
        }

        if (! is_array($value)) {
            return null;
        }

        $status = self::normalizeStatus($value['status'] ?? null);

        if ($status === null) {
            return null;
        }

        $row = ['status' => $status];

        if (array_key_exists('item_count', $value) && is_int($value['item_count'])) {
            $row['item_count'] = $value['item_count'];
        }

        if (array_key_exists('error_code', $value) && (is_string($value['error_code']) || $value['error_code'] === null)) {
            $row['error_code'] = $value['error_code'];
        }

        return $row;
    }

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

        return match ($status) {
            self::StatusSucceeded, self::StatusFailed, self::StatusSkipped => $status,
            default => null,
        };
    }

    /**
     * @return array<string, array{status: string, item_count?: int, error_code?: string|null}>
     */
    private static function normalizeCoverageMap(mixed $value): array
    {
        if (! is_array($value)) {
            return [];
        }

        $normalized = [];

        foreach ($value as $type => $meta) {
            if (! is_string($type) || $type === '') {
                continue;
            }

            if (! is_array($meta)) {
                continue;
            }

            $status = self::normalizeStatus($meta['status'] ?? null);

            if ($status === null) {
                continue;
            }

            $row = ['status' => $status];

            if (array_key_exists('item_count', $meta) && is_int($meta['item_count'])) {
                $row['item_count'] = $meta['item_count'];
            }

            if (array_key_exists('error_code', $meta) && (is_string($meta['error_code']) || $meta['error_code'] === null)) {
                $row['error_code'] = $meta['error_code'];
            }

            $normalized[$type] = $row;
        }

        ksort($normalized);

        return $normalized;
    }
}
