<?php

namespace App\Support\Providers\Boundary;

use InvalidArgumentException;

final class ProviderBoundaryCatalog
{
    public const string STATUS_ALLOWED = 'allowed';

    public const string STATUS_REVIEW_REQUIRED = 'review_required';

    public const string STATUS_BLOCKED = 'blocked';

    public const string VIOLATION_NONE = 'none';

    public const string VIOLATION_PLATFORM_CORE_PROVIDER_LEAK = 'platform_core_provider_leak';

    public const string VIOLATION_UNDECLARED_EXCEPTION = 'undeclared_exception';

    public const string VIOLATION_MISSING_PROVIDER_BINDING = 'missing_provider_binding';

    public const string VIOLATION_PROVIDER_BINDING_AS_PRIMARY_TRUTH = 'provider_binding_as_primary_truth';

    /**
     * @return array<string, ProviderBoundarySeam>
     */
    public function all(): array
    {
        $seams = config('provider_boundaries.seams', []);

        if (! is_array($seams)) {
            throw new InvalidArgumentException('Provider boundary seam catalog must be an array.');
        }

        $catalog = [];

        foreach ($seams as $key => $attributes) {
            if (! is_string($key) || ! is_array($attributes)) {
                throw new InvalidArgumentException('Provider boundary seam catalog entries must be keyed arrays.');
            }

            $catalog[$key] = ProviderBoundarySeam::fromConfig($key, $attributes);
        }

        ksort($catalog);

        return $catalog;
    }

    public function get(string $key): ProviderBoundarySeam
    {
        $normalizedKey = trim($key);
        $seam = $this->all()[$normalizedKey] ?? null;

        if (! $seam instanceof ProviderBoundarySeam) {
            throw new InvalidArgumentException("Unknown provider boundary seam: {$normalizedKey}");
        }

        return $seam;
    }

    public function has(string $key): bool
    {
        return array_key_exists(trim($key), $this->all());
    }

    /**
     * @return array{
     *     status: string,
     *     seam_key: string,
     *     file_path: string,
     *     violation_code: string,
     *     message: string,
     *     suggested_follow_up: string
     * }
     */
    public function evaluateChange(
        string $seamKey,
        string $filePath,
        ProviderBoundaryOwner|string $proposedOwner,
        array $providerSpecificTerms = [],
        bool $introducesNewBinding = false,
    ): array {
        $seam = $this->get($seamKey);
        $owner = is_string($proposedOwner)
            ? ProviderBoundaryOwner::tryFrom($proposedOwner)
            : $proposedOwner;

        if (! $owner instanceof ProviderBoundaryOwner) {
            throw new InvalidArgumentException('Proposed provider boundary owner is invalid.');
        }

        $providerSpecificTerms = $this->normalizeTerms($providerSpecificTerms);

        if ($introducesNewBinding && $seam->isPlatformCore() && $owner === ProviderBoundaryOwner::PlatformCore) {
            return $this->result(
                status: self::STATUS_BLOCKED,
                seam: $seam,
                filePath: $filePath,
                violationCode: self::VIOLATION_PROVIDER_BINDING_AS_PRIMARY_TRUTH,
                message: 'Provider binding metadata must stay explicit and secondary to the platform-core operation definition.',
                suggestedFollowUp: ProviderBoundarySeam::FOLLOW_UP_DOCUMENT_IN_FEATURE,
            );
        }

        if ($seam->isProviderOwned()) {
            return $this->result(
                status: self::STATUS_ALLOWED,
                seam: $seam,
                filePath: $filePath,
                violationCode: self::VIOLATION_NONE,
                message: 'Provider-specific semantics are allowed inside this provider-owned seam.',
                suggestedFollowUp: ProviderBoundarySeam::FOLLOW_UP_NONE,
            );
        }

        if ($providerSpecificTerms === []) {
            return $this->result(
                status: self::STATUS_ALLOWED,
                seam: $seam,
                filePath: $filePath,
                violationCode: self::VIOLATION_NONE,
                message: 'The platform-core seam does not introduce provider-specific terms.',
                suggestedFollowUp: ProviderBoundarySeam::FOLLOW_UP_NONE,
            );
        }

        $undocumentedTerms = array_values(array_filter(
            $providerSpecificTerms,
            static fn (string $term): bool => ! $seam->documentsProviderSemantic($term),
        ));

        if ($undocumentedTerms !== []) {
            return $this->result(
                status: self::STATUS_BLOCKED,
                seam: $seam,
                filePath: $filePath,
                violationCode: self::VIOLATION_PLATFORM_CORE_PROVIDER_LEAK,
                message: 'Platform-core seam contains undocumented provider-specific terms: '.implode(', ', $undocumentedTerms).'.',
                suggestedFollowUp: ProviderBoundarySeam::FOLLOW_UP_SPEC,
            );
        }

        return $this->result(
            status: self::STATUS_REVIEW_REQUIRED,
            seam: $seam,
            filePath: $filePath,
            violationCode: self::VIOLATION_NONE,
            message: 'Platform-core seam relies on documented current-release provider exception metadata.',
            suggestedFollowUp: $seam->followUpAction,
        );
    }

    /**
     * @param  array<mixed>  $terms
     * @return list<string>
     */
    private function normalizeTerms(array $terms): array
    {
        return array_values(array_filter(
            array_map(static fn (mixed $term): string => trim((string) $term), $terms),
            static fn (string $term): bool => $term !== '',
        ));
    }

    /**
     * @return array{
     *     status: string,
     *     seam_key: string,
     *     file_path: string,
     *     violation_code: string,
     *     message: string,
     *     suggested_follow_up: string
     * }
     */
    private function result(
        string $status,
        ProviderBoundarySeam $seam,
        string $filePath,
        string $violationCode,
        string $message,
        string $suggestedFollowUp,
    ): array {
        return [
            'status' => $status,
            'seam_key' => $seam->key,
            'file_path' => $filePath,
            'violation_code' => $violationCode,
            'message' => $message,
            'suggested_follow_up' => $suggestedFollowUp,
        ];
    }
}
