'pov', self::Formal, self::Balanced, self::Casual => 'formality', self::Calm, self::Moderate, self::Enthusiastic, self::Vibrant => 'energy', self::Serious, self::Dry, self::Witty, self::Playful => 'humor', self::Respectful, self::EvenHanded, self::Bold, self::Provocative => 'attitude', self::Neutral, self::Friendly, self::Empathetic => 'warmth', self::Humble, self::Confident, self::Assertive => 'confidence', default => 'style', }; } /** * Groups whose options are mutually exclusive (rendered single-select). * * @return array */ public static function singleSelectGroups(): array { return ['pov', 'formality', 'energy', 'humor', 'attitude', 'warmth', 'confidence']; } /** * @return array */ public static function values(): array { return array_map(fn (self $t) => $t->value, self::cases()); } /** * Reduces arbitrary values (e.g. an LLM's guess) to a coherent selection: * only valid traits, and at most one per single-select dimension. Style * traits stack. Order is preserved. * * @param array $values * @return array */ public static function coerce(array $values): array { $single = self::singleSelectGroups(); $usedGroups = []; $result = []; foreach ($values as $value) { $trait = is_string($value) ? self::tryFrom($value) : null; if ($trait === null) { continue; } $group = $trait->group(); if (in_array($group, $single, true)) { if (isset($usedGroups[$group])) { continue; } $usedGroups[$group] = true; } $result[] = $trait->value; } return $result; } /** * Trait values keyed by group, in declaration order — drives the brand * form's grouped pill picker. * * @return array> */ public static function grouped(): array { $grouped = []; foreach (self::cases() as $trait) { $grouped[$trait->group()][] = $trait->value; } return $grouped; } }