<?php

declare(strict_types=1);

namespace App\Config;

final class DomainVocabularyConfig
{
    public function __construct(private readonly array $config = [])
    {
    }

    /** @return string[] */
    public function view(string $path, array $fallback = []): array
    {
        $definition = $this->value('views.' . $path, null);
        if (!is_array($definition)) {
            return $this->uniqueStringList($fallback);
        }

        $terms = [];
        foreach ($this->stringListFromValue($definition['include'] ?? []) as $className) {
            foreach ($this->domainClass($className) as $term) {
                $terms[] = $term;
            }
        }
        foreach ($this->stringListFromValue($definition['add'] ?? []) as $term) {
            $terms[] = $term;
        }

        $terms = $this->uniqueStringList($terms);
        return $terms !== [] ? $terms : $this->uniqueStringList($fallback);
    }

    /** @return string[] */
    public function domainClass(string $name): array
    {
        return $this->stringList('classes.' . $name, []);
    }

    /** @return array<string, string[]> */
    public function map(string $path, array $fallback = []): array
    {
        $value = $this->value('maps.' . $path, null);
        if (!is_array($value)) {
            return $this->uniqueStringListMap($fallback);
        }

        $out = [];
        foreach ($value as $key => $items) {
            if (!is_scalar($key)) {
                continue;
            }
            $cleanKey = trim((string) $key);
            $cleanItems = $this->stringListFromValue($items);
            if ($cleanKey !== '' && $cleanItems !== []) {
                $out[$cleanKey] = $cleanItems;
            }
        }

        return $out !== [] ? $out : $this->uniqueStringListMap($fallback);
    }

    /** @return array<string, string> */
    public function stringMap(string $path, array $fallback = []): array
    {
        $value = $this->value('maps.' . $path, null);
        if (!is_array($value)) {
            return $this->uniqueStringMap($fallback);
        }

        $out = [];
        foreach ($value as $key => $mappedValue) {
            if (!is_scalar($key)) {
                continue;
            }

            $cleanKey = trim((string) $key);
            if ($cleanKey === '') {
                continue;
            }

            if (is_array($mappedValue)) {
                $items = $this->stringListFromValue($mappedValue);
                $mappedValue = $items[0] ?? '';
            }

            if (!is_scalar($mappedValue)) {
                continue;
            }

            $cleanValue = trim((string) $mappedValue);
            if ($cleanValue !== '') {
                $out[$cleanKey] = $cleanValue;
            }
        }

        return $out !== [] ? $out : $this->uniqueStringMap($fallback);
    }

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

    /** @return string[] */
    private function stringList(string $path, array $fallback): array
    {
        $value = $this->value($path, null);
        $items = $this->stringListFromValue($value);
        return $items !== [] ? $items : $this->uniqueStringList($fallback);
    }

    /** @return string[] */
    private function stringListFromValue(mixed $value): array
    {
        if (!is_array($value)) {
            return [];
        }

        return $this->uniqueStringList($value);
    }

    /** @return string[] */
    private function uniqueStringList(array $items): array
    {
        $out = [];
        foreach ($items as $item) {
            if (!is_scalar($item)) {
                continue;
            }

            $item = trim((string) $item);
            if ($item === '' || in_array($item, $out, true)) {
                continue;
            }

            $out[] = $item;
        }

        return $out;
    }

    /** @return array<string, string[]> */
    private function uniqueStringListMap(array $map): array
    {
        $out = [];
        foreach ($map as $key => $items) {
            if (!is_scalar($key)) {
                continue;
            }

            $cleanKey = trim((string) $key);
            $cleanItems = $this->uniqueStringList(is_array($items) ? $items : []);
            if ($cleanKey !== '' && $cleanItems !== []) {
                $out[$cleanKey] = $cleanItems;
            }
        }

        return $out;
    }

    /** @return array<string, string> */
    private function uniqueStringMap(array $map): array
    {
        $out = [];
        foreach ($map as $key => $value) {
            if (!is_scalar($key) || !is_scalar($value)) {
                continue;
            }

            $cleanKey = trim((string) $key);
            $cleanValue = trim((string) $value);
            if ($cleanKey !== '' && $cleanValue !== '') {
                $out[$cleanKey] = $cleanValue;
            }
        }

        return $out;
    }

    private function value(string $path, mixed $fallback): mixed
    {
        $current = $this->config;
        foreach (explode('.', $path) as $segment) {
            if (!is_array($current) || !array_key_exists($segment, $current)) {
                return $fallback;
            }

            $current = $current[$segment];
        }

        return $current;
    }
}
