Lines 100.00% 25 / 25
Methods 100.00% 6 / 6
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 __construct 100.00% 2 / 2 100.00% 1 / 1 1
 resolvePhpType 100.00% 4 / 4 100.00% 1 / 1 3
 phpTypeForKind 100.00% 6 / 6 100.00% 1 / 1 4
 phpTypeForConstant 100.00% 2 / 2 100.00% 1 / 1 2
 phpTypeForSelect 100.00% 4 / 4 100.00% 1 / 1 3
 selectOptionValues 100.00% 7 / 7 100.00% 1 / 1 2
10final readonly class ServiceSelector
11{
12    private const string ANY_ARRAY = 'array<array-key, mixed>';
13
14    private const string DURATION = 'array{days?: int|float, hours?: int|float, minutes?: int|float, seconds?: int|float, milliseconds?: int|float}|string|int|float';
15
16    /** @var array<string, array{0: string, 1?: string}> */
17    private const array KIND_TYPES = [
18        'action' => ['array', self::ANY_ARRAY],
19        'addon' => ['string'],
20        'area' => ['string'],
21        'assist_pipeline' => ['string'],
22        'attribute' => ['string'],
23        'backup_location' => ['string'],
24        'boolean' => ['bool'],
25        'color_rgb' => ['array', 'array{int, int, int}'],
26        'color_temp' => ['int|float'],
27        'condition' => ['array', self::ANY_ARRAY],
28        'config_entry' => ['string'],
29        'conversation_agent' => ['string'],
30        'country' => ['string'],
31        'date' => ['string'],
32        'datetime' => ['string'],
33        'device' => ['string'],
34        'duration' => ['array|string|int|float', self::DURATION],
35        'entity' => ['string'],
36        'file' => ['string'],
37        'floor' => ['string'],
38        'icon' => ['string'],
39        'label' => ['string'],
40        'language' => ['string'],
41        'location' => ['array', self::ANY_ARRAY],
42        'media' => ['array', self::ANY_ARRAY],
43        'navigation' => ['string'],
44        'number' => ['int|float'],
45        'object' => ['array', self::ANY_ARRAY],
46        'qr_code' => ['string'],
47        'selector' => ['array', self::ANY_ARRAY],
48        'state' => ['string'],
49        'statistic' => ['string'],
50        'target' => ['array', self::ANY_ARRAY],
51        'template' => ['string'],
52        'text' => ['string|int|float'],
53        'theme' => ['string'],
54        'time' => ['string'],
55        'trigger' => ['array', self::ANY_ARRAY],
56        'tts' => ['string'],
57        'ui_action' => ['array', self::ANY_ARRAY],
58        'ui_color' => ['string'],
59    ];
60
61    private const string SELECT = 'select';
62
63    private const string CONSTANT = 'constant';
64
65    private const string CONSTANT_VALUE = 'value';
66
67    public ?string $kind;
68
69    public RawMap $configuration;
70
71    public function __construct(RawMap $selector)
72    {
73        $this->kind = $selector->listKeys()[0] ?? null;
74        $this->configuration = $selector->readMap($this->kind ?? '');
75    }
76
77    public function resolvePhpType(): PhpType
78    {
79        if ($this->kind === null) {
80            return PhpType::mixed();
81        }
82
83        $type = $this->phpTypeForKind($this->kind);
84
85        return $this->configuration->readBool('multiple') ? $type->listOf() : $type;
86    }
87
88    private function phpTypeForKind(string $kind): PhpType
89    {
90        if ($kind === self::SELECT) {
91            return $this->phpTypeForSelect();
92        }
93
94        if ($kind === self::CONSTANT) {
95            return $this->phpTypeForConstant();
96        }
97
98        $type = self::KIND_TYPES[$kind] ?? null;
99
100        return $type === null ? PhpType::mixed() : PhpType::fromNative(...$type);
101    }
102
103    private function phpTypeForConstant(): PhpType
104    {
105        $value = $this->configuration->readScalar(self::CONSTANT_VALUE);
106
107        return $value === null ? PhpType::mixed() : PhpType::fromNative(get_debug_type($value), var_export($value, true));
108    }
109
110    private function phpTypeForSelect(): PhpType
111    {
112        $options = $this->selectOptionValues();
113
114        if ($options === [] || $this->configuration->readBool('custom_value')) {
115            return PhpType::fromNative('string');
116        }
117
118        return PhpType::fromNative('string', implode('|', array_map(static fn(string $option): string => var_export($option, true), $options)));
119    }
120
121    /** @return list<string> */
122    private function selectOptionValues(): array
123    {
124        $options = $this->configuration->listStrings('options');
125
126        if ($options !== []) {
127            return $options;
128        }
129
130        return array_values(array_filter(
131            array_map(static fn(RawMap $option): ?string => $option->readString('value'), $this->configuration->listMaps('options')),
132            static fn(?string $value): bool => $value !== null,
133        ));
134    }
135}