<?php

namespace App\Support\Providers\Boundary;

use InvalidArgumentException;

final class ProviderBoundarySeam
{
    public const string FOLLOW_UP_NONE = 'none';

    public const string FOLLOW_UP_DOCUMENT_IN_FEATURE = 'document-in-feature';

    public const string FOLLOW_UP_SPEC = 'follow-up-spec';

    /**
     * @param  list<string>  $implementationPaths
     * @param  list<string>  $neutralTerms
     * @param  list<string>  $retainedProviderSemantics
     */
    public function __construct(
        public readonly string $key,
        public readonly ProviderBoundaryOwner $owner,
        public readonly string $description,
        public readonly array $implementationPaths,
        public readonly array $neutralTerms,
        public readonly array $retainedProviderSemantics,
        public readonly string $followUpAction,
    ) {
        $this->validate();
    }

    /**
     * @param  array{
     *     owner?: string,
     *     description?: string,
     *     implementation_paths?: list<string>,
     *     neutral_terms?: list<string>,
     *     retained_provider_semantics?: list<string>,
     *     follow_up_action?: string
     * }  $attributes
     */
    public static function fromConfig(string $key, array $attributes): self
    {
        $owner = ProviderBoundaryOwner::tryFrom((string) ($attributes['owner'] ?? ''));

        if (! $owner instanceof ProviderBoundaryOwner) {
            throw new InvalidArgumentException("Provider boundary seam [{$key}] has an invalid owner.");
        }

        return new self(
            key: $key,
            owner: $owner,
            description: (string) ($attributes['description'] ?? ''),
            implementationPaths: self::stringList($attributes['implementation_paths'] ?? []),
            neutralTerms: self::stringList($attributes['neutral_terms'] ?? []),
            retainedProviderSemantics: self::stringList($attributes['retained_provider_semantics'] ?? []),
            followUpAction: (string) ($attributes['follow_up_action'] ?? self::FOLLOW_UP_NONE),
        );
    }

    public function isProviderOwned(): bool
    {
        return $this->owner === ProviderBoundaryOwner::ProviderOwned;
    }

    public function isPlatformCore(): bool
    {
        return $this->owner === ProviderBoundaryOwner::PlatformCore;
    }

    public function retainsProviderSemantics(): bool
    {
        return $this->retainedProviderSemantics !== [];
    }

    public function documentsProviderSemantic(string $term): bool
    {
        return in_array($term, $this->retainedProviderSemantics, true);
    }

    public function coversPath(string $path): bool
    {
        $normalizedPath = $this->normalizePath($path);

        foreach ($this->implementationPaths as $implementationPath) {
            if ($normalizedPath === $this->normalizePath($implementationPath)) {
                return true;
            }
        }

        return false;
    }

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

    private function validate(): void
    {
        if (trim($this->key) === '') {
            throw new InvalidArgumentException('Provider boundary seam key cannot be empty.');
        }

        if (trim($this->description) === '') {
            throw new InvalidArgumentException("Provider boundary seam [{$this->key}] must include a description.");
        }

        if ($this->implementationPaths === []) {
            throw new InvalidArgumentException("Provider boundary seam [{$this->key}] must include implementation paths.");
        }

        if ($this->isPlatformCore() && $this->neutralTerms === []) {
            throw new InvalidArgumentException("Platform-core provider boundary seam [{$this->key}] must include neutral terms.");
        }

        if ($this->retainsProviderSemantics() && $this->followUpAction === self::FOLLOW_UP_NONE) {
            throw new InvalidArgumentException("Provider boundary seam [{$this->key}] retains provider semantics without a follow-up action.");
        }

        if (! in_array($this->followUpAction, $this->validFollowUpActions(), true)) {
            throw new InvalidArgumentException("Provider boundary seam [{$this->key}] has an invalid follow-up action.");
        }
    }

    /**
     * @return list<string>
     */
    private function validFollowUpActions(): array
    {
        return [
            self::FOLLOW_UP_NONE,
            self::FOLLOW_UP_DOCUMENT_IN_FEATURE,
            self::FOLLOW_UP_SPEC,
        ];
    }

    private function normalizePath(string $path): string
    {
        return trim(str_replace('\\', '/', $path), '/');
    }
}
