<?php

declare(strict_types=1);

namespace App\Config;

final class AgentRunnerConfig
{
    /**
     * @param array<string, mixed> $config
     */
    public function __construct(
        private readonly array $config = [],
    ) {
    }

    public function getCommerceHistoryBudgetChars(): int
    {
        return $this->getRequiredInt('commerce_history_budget_chars');
    }

    public function getProductSearchKnowledgeChunkLimit(): int
    {
        return $this->getRequiredInt('product_search_knowledge_chunk_limit');
    }

    public function getAdvisoryProductSearchKnowledgeChunkLimit(): int
    {
        return $this->getRequiredInt('advisory_product_search_knowledge_chunk_limit');
    }

    public function getOptimizedShopQueryPrefixPattern(): string
    {
        return $this->getRequiredString('optimized_shop_query_prefix_pattern');
    }

    public function getOptimizedShopQueryTrimCharacters(): string
    {
        return $this->getRequiredString('optimized_shop_query_trim_characters');
    }

    /**
     * @return string[]
     */
    public function getFollowUpStrongReferencePatterns(): array
    {
        return $this->getRequiredStringList('follow_up_context.strong_reference_patterns');
    }

    /**
     * @return string[]
     */
    public function getFollowUpExplicitCommercialSignalTerms(): array
    {
        return $this->getRequiredStringList('follow_up_context.explicit_commercial_signal_terms');
    }

    public function isCommercialTableFollowUpEnabled(): bool
    {
        return $this->getRequiredBool('follow_up_context.commercial_table_follow_up.enabled');
    }

    /**
     * @return string[]
     */
    public function getCommercialTableFollowUpPromptPatterns(): array
    {
        return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.prompt_patterns');
    }

    /**
     * @return string[]
     */
    public function getCommercialTableFollowUpHistoryAnchorPatterns(): array
    {
        return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.history_anchor_patterns');
    }

    /**
     * @return string[]
     */
    public function getCommercialTableFollowUpTableTerms(): array
    {
        return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.table_terms');
    }

    /**
     * @return string[]
     */
    public function getCommercialTableFollowUpCommercialTerms(): array
    {
        return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.commercial_terms');
    }

    /**
     * @return string[]
     */
    public function getCommercialTableFollowUpIndicatorMarkerPatterns(): array
    {
        return $this->getRequiredStringList('follow_up_context.commercial_table_follow_up.indicator_marker_patterns');
    }

    public function getCommercialTableFollowUpQueryTemplateWithModel(): string
    {
        return $this->getRequiredString('follow_up_context.commercial_table_follow_up.query_template_with_model');
    }

    public function getCommercialTableFollowUpQueryTemplateWithoutModel(): string
    {
        return $this->getRequiredString('follow_up_context.commercial_table_follow_up.query_template_without_model');
    }

    public function getFollowUpHistoryQuestionPattern(): string
    {
        return $this->getRequiredString('follow_up_context.history_question_pattern');
    }

    public function getFollowUpHistoryTurnSplitPattern(): string
    {
        return $this->getRequiredString('follow_up_context.history_turn_split_pattern');
    }

    public function getFollowUpHistoryQuestionStripPattern(): string
    {
        return $this->getRequiredString('follow_up_context.history_question_strip_pattern');
    }

    public function getFollowUpReferenceAnchorProductModelPattern(): string
    {
        $value = $this->optionalValue('follow_up_context.reference_anchor.product_model_pattern');
        if (is_string($value) && trim($value) !== '') {
            return $value;
        }

        return $this->getRequiredString('follow_up_context.reference_anchor.testomat_model_pattern');
    }

    public function getFollowUpReferenceAnchorMeasurementValuePattern(): string
    {
        $value = $this->optionalValue('follow_up_context.reference_anchor.measurement_value_pattern');
        if (is_string($value) && trim($value) !== '') {
            return $value;
        }

        return $this->getRequiredString('follow_up_context.reference_anchor.hardness_value_pattern');
    }

    public function getFollowUpReferenceAnchorTestomatModelPattern(): string
    {
        return $this->getFollowUpReferenceAnchorProductModelPattern();
    }

    public function getFollowUpReferenceAnchorHardnessValuePattern(): string
    {
        return $this->getFollowUpReferenceAnchorMeasurementValuePattern();
    }


    public function getFollowUpContextPreviousUserQuestionTemplate(): string
    {
        return $this->getRequiredString('follow_up_context.context_labels.previous_user_question_template');
    }

    public function getFollowUpContextPreviousReferenceAnchorsTemplate(): string
    {
        return $this->getRequiredString('follow_up_context.context_labels.previous_reference_anchors_template');
    }

    public function getFollowUpContextCurrentQuestionTemplate(): string
    {
        return $this->getRequiredString('follow_up_context.context_labels.current_follow_up_question_template');
    }

    public function isInputNormalizationEnabled(): bool
    {
        return $this->getRequiredBool('input_normalization.enabled');
    }

    public function getInputNormalizationMaxInputChars(): int
    {
        return $this->getRequiredInt('input_normalization.max_input_chars');
    }

    public function getInputNormalizationMaxOutputChars(): int
    {
        return $this->getRequiredInt('input_normalization.max_output_chars');
    }

    public function getInputNormalizationMaxAddedTokens(): int
    {
        return $this->getRequiredInt('input_normalization.max_added_tokens');
    }

    public function getInputNormalizationMaxLengthRatioPercent(): int
    {
        return $this->getRequiredInt('input_normalization.max_length_ratio_percent');
    }

    public function getInputNormalizationHeartbeatMessage(): string
    {
        return $this->getRequiredString('input_normalization.heartbeat_message');
    }

    public function getInputNormalizationOutputPrefixPattern(): string
    {
        return $this->getRequiredString('input_normalization.output_prefix_pattern');
    }

    /**
     * @return string[]
     */
    public function getInputNormalizationPlaceholderOutputs(): array
    {
        return $this->getRequiredStringList('input_normalization.placeholder_outputs');
    }

    /**
     * @return string[]
     */
    public function getInputNormalizationSkipPatterns(): array
    {
        return $this->getRequiredStringList('input_normalization.skip_patterns');
    }

    public function getInputNormalizationPrompt(string $prompt): string
    {
        return $this->implodePromptBlocks([
            $this->getInputNormalizationIntro(),
            $this->buildRulesBlock($this->getInputNormalizationRules()),
            $this->getInputNormalizationOutputFormatBlock(),
            $this->getInputNormalizationCurrentUserInputLabel() . ':',
            trim($prompt),
        ]);
    }

    /**
     * @return string[]
     */
    public function getInputNormalizationRules(): array
    {
        return $this->getRequiredStringList('input_normalization.prompt.rules');
    }

    public function getInputNormalizationIntro(): string
    {
        return $this->getRequiredString('input_normalization.prompt.intro');
    }

    public function getInputNormalizationOutputFormatBlock(): string
    {
        return $this->getRequiredString('input_normalization.prompt.output_format_block');
    }

    public function getInputNormalizationCurrentUserInputLabel(): string
    {
        return $this->getRequiredString('input_normalization.prompt.current_user_input_label');
    }

    public function isInputNormalizationFuzzyRoutingEnabled(): bool
    {
        return $this->getRequiredBool('input_normalization.fuzzy_routing.enabled');
    }

    public function getInputNormalizationFuzzyRoutingMinTokenLength(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.min_token_length');
    }

    public function getInputNormalizationFuzzyRoutingMediumTokenLength(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.medium_token_length');
    }

    public function getInputNormalizationFuzzyRoutingLongTokenLength(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.long_token_length');
    }

    public function getInputNormalizationFuzzyRoutingMaxDistanceShort(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.max_distance_short');
    }

    public function getInputNormalizationFuzzyRoutingMaxDistanceMedium(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.max_distance_medium');
    }

    public function getInputNormalizationFuzzyRoutingMaxDistanceLong(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.max_distance_long');
    }

    public function getInputNormalizationFuzzyRoutingMinSimilarityPercent(): int
    {
        return $this->getRequiredInt('input_normalization.fuzzy_routing.min_similarity_percent');
    }

    /**
     * @return string[]
     */
    public function getInputNormalizationFuzzyRoutingTerms(): array
    {
        return $this->getRequiredStringList('input_normalization.fuzzy_routing.terms');
    }

    private function getRequiredInt(string $key): int
    {
        $value = $this->requiredValue($key);

        if (is_numeric($value)) {
            return (int) $value;
        }

        throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be numeric.', $key));
    }

    private function getRequiredBool(string $key): bool
    {
        $value = $this->requiredValue($key);

        if (is_bool($value)) {
            return $value;
        }

        if (is_scalar($value)) {
            $normalized = strtolower(trim((string) $value));

            if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
                return true;
            }

            if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
                return false;
            }
        }

        throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be boolean.', $key));
    }

    private function getRequiredString(string $key): string
    {
        $value = $this->requiredValue($key);

        if (is_string($value) && $value !== '') {
            return $value;
        }

        throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a non-empty string.', $key));
    }

    private function getOptionalBool(string $key, bool $default): bool
    {
        $value = $this->optionalValue($key);

        if ($value === null) {
            return $default;
        }

        if (is_bool($value)) {
            return $value;
        }

        if (is_scalar($value)) {
            $normalized = strtolower(trim((string) $value));

            if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
                return true;
            }

            if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
                return false;
            }
        }

        throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be boolean.', $key));
    }

    /**
     * @return string[]
     */
    private function getOptionalStringList(string $key): array
    {
        $value = $this->optionalValue($key);

        if ($value === null) {
            return [];
        }

        if (!is_array($value)) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a list.', $key));
        }

        $out = [];

        foreach ($value as $item) {
            if (!is_scalar($item)) {
                continue;
            }

            $item = trim((string) $item);

            if ($item !== '') {
                $out[] = $item;
            }
        }

        return array_values(array_unique($out));
    }

    /**
     * @return string[]
     */
    private function getRequiredStringList(string $key): array
    {
        $value = $this->requiredValue($key);

        if (!is_array($value)) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a list.', $key));
        }

        $out = [];

        foreach ($value as $item) {
            if (!is_scalar($item)) {
                continue;
            }

            $item = trim((string) $item);

            if ($item !== '') {
                $out[] = $item;
            }
        }

        if ($out === []) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one non-empty value.', $key));
        }

        return $out;
    }


    /**
     * @return array<int, array{label:string, prompt:string}>
     */
    private function getRequiredActionList(string $key): array
    {
        $value = $this->requiredValue($key);

        if (!is_array($value)) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a list of action definitions.', $key));
        }

        $out = [];

        foreach ($value as $item) {
            if (!is_array($item)) {
                continue;
            }

            $label = isset($item['label']) && is_scalar($item['label']) ? trim((string) $item['label']) : '';
            $prompt = isset($item['prompt']) && is_scalar($item['prompt']) ? trim((string) $item['prompt']) : '';

            if ($label === '' || $prompt === '') {
                continue;
            }

            $out[] = [
                'label' => $label,
                'prompt' => $prompt,
            ];
        }

        if ($out === []) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one valid action definition.', $key));
        }

        return $out;
    }

    /**
     * @return array<string, string[]>
     */
    private function getRequiredStringListMap(string $key): array
    {
        $value = $this->requiredValue($key);

        if (!is_array($value)) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a map of string lists.', $key));
        }

        $out = [];

        foreach ($value as $mapKey => $items) {
            if (!is_scalar($mapKey) || !is_array($items)) {
                continue;
            }

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

            $terms = [];
            foreach ($items as $item) {
                if (!is_scalar($item)) {
                    continue;
                }

                $item = trim((string) $item);
                if ($item !== '' && !in_array($item, $terms, true)) {
                    $terms[] = $item;
                }
            }

            if ($terms !== []) {
                $out[$mapKey] = $terms;
            }
        }

        if ($out === []) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must contain at least one valid entry.', $key));
        }

        return $out;
    }

    /**
     * @return array<string, string>
     */
    private function getOptionalStringMap(string $key): array
    {
        $value = $this->optionalValue($key);

        if ($value === null) {
            return [];
        }

        if (!is_array($value)) {
            throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" must be a string map.', $key));
        }

        $out = [];

        foreach ($value as $mapKey => $mapValue) {
            if (!is_scalar($mapKey) || !is_scalar($mapValue)) {
                continue;
            }

            $mapKey = trim((string) $mapKey);
            $mapValue = trim((string) $mapValue);

            if ($mapKey !== '' && $mapValue !== '') {
                $out[$mapKey] = $mapValue;
            }
        }

        return $out;
    }


    private function requiredValue(string $key): mixed
    {
        $current = $this->config;

        foreach (explode('.', $key) as $segment) {
            if (!is_array($current) || !array_key_exists($segment, $current)) {
                throw new \InvalidArgumentException(sprintf('RetrieX agent config key "%s" is required.', $key));
            }

            $current = $current[$segment];
        }

        return $current;
    }

    private function optionalValue(string $key): mixed
    {
        $current = $this->config;

        foreach (explode('.', $key) as $segment) {
            if (!is_array($current) || !array_key_exists($segment, $current)) {
                return null;
            }

            $current = $current[$segment];
        }

        return $current;
    }

    public function getEmptyPromptMessage(): string
    {
        return $this->getRequiredString('messages.empty_prompt');
    }

    public function getAnalyzeRequestMessage(): string
    {
        return $this->getRequiredString('messages.analyze_request');
    }

    public function getCheckInternetSourcesMessage(): string
    {
        return $this->getRequiredString('messages.check_internet_sources');
    }

    public function getRetrieveKnowledgeMessage(): string
    {
        return $this->getRequiredString('messages.retrieve_knowledge');
    }

    public function getOptimizeSearchMessage(): string
    {
        return $this->getRequiredString('messages.optimize_search');
    }

    public function getNoConcreteShopQueryMessage(): string
    {
        return $this->getRequiredString('messages.no_concrete_shop_query');
    }

    public function getFetchSearchDataMessageTemplate(): string
    {
        return $this->getRequiredString('messages.fetch_search_data_template');
    }

    public function getAnalyzeAllInformationMessage(): string
    {
        return $this->getRequiredString('messages.analyze_all_information');
    }

    public function getThinkingWhileStreamingMessage(): string
    {
        return $this->getRequiredString('messages.thinking_while_streaming');
    }

    public function getNoLlmDataReceivedMessage(): string
    {
        return $this->getRequiredString('messages.no_llm_data_received');
    }

    public function getNoLlmFallbackMaxShopResults(): int
    {
        return $this->getRequiredInt('no_llm_fallback.max_shop_results');
    }

    public function getRagEvidenceCleanupProfile(): string
    {
        return $this->getRequiredString('rag_evidence_guard.cleanup_profile');
    }

    /**
     * @return string[]
     */
    public function getRagEvidenceStopTerms(): array
    {
        return $this->getRequiredStringList('rag_evidence_guard.stop_terms');
    }

    /**
     * @return array<string, string[]>
     */
    public function getRagEvidenceSynonyms(): array
    {
        return $this->getRequiredStringListMap('rag_evidence_guard.synonyms');
    }

    /**
     * @return string[]
     */
    public function getRagEvidenceAggregateQueryPatterns(): array
    {
        return $this->getRequiredStringList('rag_evidence_guard.aggregate_query_patterns');
    }

    /**
     * @return string[]
     */
    public function getRagEvidenceAggregateEvidenceTerms(): array
    {
        return $this->getRequiredStringList('rag_evidence_guard.aggregate_evidence_terms');
    }

    /**
     * @return string[]
     */
    public function getRagEvidenceAggregateAnswerEvidencePatterns(): array
    {
        return $this->getRequiredStringList('rag_evidence_guard.aggregate_answer_evidence_patterns');
    }

    public function getNoLlmFallbackShopOnlyMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.shop_only');
    }

    public function getNoLlmFallbackShopWithKnowledgeMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.shop_with_knowledge');
    }

    public function getNoLlmFallbackAccessoryOnlyForMainDeviceMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.accessory_only_for_main_device');
    }

    public function getNoLlmFallbackEscalationMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.escalation');
    }

    public function getNoLlmFallbackKnowledgeOnlyMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.knowledge_only');
    }

    public function getNoLlmFallbackNoDataMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.no_data');
    }


    public function getShopRepairCheckMessage(): string
    {
        return $this->getRequiredString('messages.shop_repair_check');
    }

    public function getShopQueryOptimizationHeartbeatMessage(): string
    {
        return $this->getRequiredString('messages.shop_query_optimization_heartbeat');
    }

    public function getProductionUiStageLabel(string $key): string
    {
        return $this->getRequiredString('production_ui.stage_labels.' . $key);
    }

    public function getProductionUiConfidenceLabel(string $key): string
    {
        return $this->getRequiredString('production_ui.confidence_labels.' . $key);
    }

    public function getProductionUiText(string $key): string
    {
        return $this->getRequiredString('production_ui.text.' . $key);
    }

    public function getProductionUiTemplate(string $key): string
    {
        return $this->getRequiredString('production_ui.templates.' . $key);
    }

    public function getProductionUiShopResultsMaxCards(): int
    {
        return $this->getRequiredInt('production_ui.shop_results.max_cards');
    }

    /**
     * @return array<int, array{label:string, prompt:string}>
     */
    public function getProductionUiFollowUpActions(string $group): array
    {
        return $this->getRequiredActionList('production_ui.follow_up_actions.' . $group);
    }

    public function getNoLlmProductField(string $key): string
    {
        return $this->getRequiredString('no_llm_fallback.product_fields.' . $key);
    }

    public function getNoLlmFallbackNoShopResultsWithKnowledgeMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.no_shop_results_with_knowledge');
    }

    public function getNoLlmFallbackNoShopResultsNoKnowledgeMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.no_shop_results_no_knowledge');
    }

    /**
     * @return string[]
     */
    public function getNoLlmMainDeviceRequestRoleKeywords(): array
    {
        return $this->getRequiredStringList('no_llm_fallback.product_roles.main_device_request_keywords');
    }

    /**
     * @return string[]
     */
    public function getNoLlmAccessoryProductRoleKeywords(): array
    {
        return $this->getRequiredStringList('no_llm_fallback.product_roles.accessory_product_keywords');
    }

    public function getNoLlmFallbackShopUnavailableWithKnowledgeMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.shop_unavailable_with_knowledge');
    }

    public function getNoLlmFallbackShopUnavailableNoKnowledgeMessage(): string
    {
        return $this->getRequiredString('no_llm_fallback.messages.shop_unavailable_no_knowledge');
    }

    public function getGenericInternalErrorMessage(): string
    {
        return $this->getRequiredString('messages.generic_internal_error');
    }

    public function getDebugInternalErrorPrefix(): string
    {
        return $this->getRequiredString('messages.debug_internal_error_prefix');
    }

    public function getExternalUrlSourceLabel(): string
    {
        return $this->getRequiredString('source_labels.external_url');
    }

    public function getRagKnowledgeSourceLabel(): string
    {
        return $this->getRequiredString('source_labels.rag_knowledge');
    }

    public function getConversationHistorySourceLabel(): string
    {
        return $this->getRequiredString('source_labels.conversation_history');
    }

    public function getShopSystemSourceLabel(): string
    {
        return $this->getRequiredString('source_labels.shop_system');
    }

    public function getExtendedShopSearchSourceLabel(): string
    {
        return $this->getRequiredString('source_labels.extended_shop_search');
    }

    public function getUsedSourcesPrefix(): string
    {
        return $this->getRequiredString('source_labels.used_sources_prefix');
    }

    public function getSourcesPrefix(): string
    {
        return $this->getRequiredString('source_labels.sources_prefix');
    }

    public function getSourceBadgeHtmlTemplate(): string
    {
        return $this->getRequiredString('html.source_badge_template');
    }

    public function getErrorHtmlTemplate(): string
    {
        return $this->getRequiredString('html.error_template');
    }

    public function getThinkHtmlTemplate(): string
    {
        return $this->getRequiredString('html.think_template');
    }

    public function getInfoHtmlTemplate(): string
    {
        return $this->getRequiredString('html.info_template');
    }

    public function getDebugHtmlTemplate(): string
    {
        return $this->getRequiredString('html.debug_template');
    }
    public function getShopPrompt(string $prompt, string $commerceHistoryContext = ''): string
    {
        $historyBlock = '';

        if (trim($commerceHistoryContext) !== '') {
            $historyBlock = $this->buildHistoryBlock($commerceHistoryContext);
        }

        return $this->implodePromptBlocks([
            $this->getShopPromptIntro(),
            $this->buildRulesBlock($this->getShopPromptRules()),
            $this->getShopPromptOutputFormatBlock(),
            $historyBlock,
            $this->getCurrentUserInputLabel() . ':',
            trim($prompt),
        ]);
    }

    private function buildHistoryBlock(string $commerceHistoryContext): string
    {
        return $this->implodePromptBlocks([
            $this->getRecentConversationContextLabel() . ':',
            trim($commerceHistoryContext),
            $this->buildRulesBlock($this->getConversationContextRules(), 'Additional rules for conversation context:'),
        ]);
    }

    /**
     * @return string[]
     */
    public function getShopPromptRules(): array
    {
        return $this->getRequiredStringList('shop_prompt.rules');
    }

    /**
     * @return string[]
     */
    public function getConversationContextRules(): array
    {
        return $this->getRequiredStringList('shop_prompt.conversation_context_rules');
    }

    /**
     * @return string[]
     */
    public function getShopQueryContextUsageReferentialTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.context_usage.referential_terms');
    }

    public function isShopQueryCurrentInputPreservationEnabled(): bool
    {
        return $this->getOptionalBool('shop_prompt.current_input_preservation.enabled', true);
    }

    /**
     * @return string[]
     */
    public function getShopQueryCurrentInputPreservationTerms(): array
    {
        return $this->getOptionalStringList('shop_prompt.current_input_preservation.terms');
    }

    public function isShopQueryProductAttributeCleanupEnabled(): bool
    {
        return $this->getRequiredBool('shop_prompt.product_attribute_query_cleanup.enabled');
    }

    public function getShopQueryProductAttributeCleanupMinTokens(): int
    {
        return $this->getRequiredInt('shop_prompt.product_attribute_query_cleanup.min_query_tokens_after_cleanup');
    }

    /**
     * @return string[]
     */
    public function getShopQueryProductAttributeCleanupProductTypeTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.product_type_terms');
    }

    /**
     * @return string[]
     */
    public function getShopQueryProductAttributeCleanupStopTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.stop_terms');
    }

    /**
     * @return string[]
     */
    public function getShopQueryProductAttributeCleanupComparativeConstraintPatterns(): array
    {
        return $this->getRequiredStringList('shop_prompt.product_attribute_query_cleanup.comparative_constraint_patterns');
    }

    public function getShopPromptIntro(): string
    {
        return $this->getRequiredString('shop_prompt.intro');
    }

    public function getShopPromptOutputFormatBlock(): string
    {
        return $this->getRequiredString('shop_prompt.output_format_block');
    }

    public function getRecentConversationContextLabel(): string
    {
        return $this->getRequiredString('shop_prompt.recent_conversation_context_label');
    }

    public function getCurrentUserInputLabel(): string
    {
        return $this->getRequiredString('shop_prompt.current_user_input_label');
    }

    public function isShopQueryLanguagePreservationEnabled(): bool
    {
        return $this->getRequiredBool('shop_prompt.language_preservation.enabled');
    }

    /**
     * @return array<string, string[]>
     */
    public function getShopQueryLanguageMarkers(): array
    {
        $value = $this->requiredValue('shop_prompt.language_preservation.language_markers');

        if (!is_array($value)) {
            throw new \InvalidArgumentException('RetrieX agent config key "shop_prompt.language_preservation.language_markers" must be a map of marker lists.');
        }

        $out = [];

        foreach ($value as $language => $markers) {
            if (!is_string($language) || !is_array($markers)) {
                continue;
            }

            $cleanMarkers = [];

            foreach ($markers as $marker) {
                if (!is_scalar($marker)) {
                    continue;
                }

                $marker = strtolower((string) $marker);

                if ($marker !== '') {
                    $cleanMarkers[] = $marker;
                }
            }

            if ($cleanMarkers !== []) {
                $out[$language] = array_values(array_unique($cleanMarkers));
            }
        }

        if ($out === []) {
            throw new \InvalidArgumentException('RetrieX agent config key "shop_prompt.language_preservation.language_markers" must contain at least one valid marker list.');
        }

        return $out;
    }

    public function isShopQueryMetaGuardEnabled(): bool
    {
        return $this->getRequiredBool('shop_prompt.meta_query_guard.enabled');
    }

    public function getShopQueryContextFallbackCleanupProfile(): string
    {
        $value = $this->optionalValue('shop_prompt.meta_query_guard.cleanup_profile');

        if (is_string($value) && trim($value) !== '') {
            return trim($value);
        }

        return 'shop_context_fallback';
    }

    /**
     * @return string[]
     */
    public function getShopQueryMetaOnlyTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.meta_query_guard.meta_only_terms');
    }

    public function isShopQueryContextFallbackEnabled(): bool
    {
        return $this->getRequiredBool('shop_prompt.meta_query_guard.context_fallback_enabled');
    }

    public function getShopQueryContextFallbackQuestionLimit(): int
    {
        return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_question_limit');
    }

    public function getShopQueryContextFallbackHistoryBudgetChars(): int
    {
        return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_history_budget_chars');
    }

    public function shouldUseFullHistoryForShopQueryContextFallback(): bool
    {
        return $this->getRequiredBool('shop_prompt.meta_query_guard.context_fallback_use_full_history');
    }

    public function getShopQueryContextFallbackMaxTerms(): int
    {
        return $this->getRequiredInt('shop_prompt.meta_query_guard.context_fallback_max_terms');
    }

    /**
     * @return string[]
     */
    public function getShopQueryContextFallbackFilterTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.meta_query_guard.context_fallback_filter_terms');
    }

    public function isShopQueryContextAnchorEnrichmentEnabled(): bool
    {
        return $this->getRequiredBool('shop_prompt.context_anchor_enrichment.enabled');
    }

    public function getShopQueryContextAnchorEnrichmentMaxQueryTerms(): int
    {
        return $this->getRequiredInt('shop_prompt.context_anchor_enrichment.max_query_terms');
    }

    /**
     * @return string[]
     */
    public function getShopQueryContextAnchorEnrichmentTriggerTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.context_anchor_enrichment.trigger_terms');
    }

    /**
     * @return string[]
     */
    public function getShopQueryContextAnchorEnrichmentPatterns(): array
    {
        return $this->getRequiredStringList('shop_prompt.context_anchor_enrichment.anchor_patterns');
    }

    public function getShopQueryContextAnchorEnrichmentTemplate(): string
    {
        return $this->getRequiredString('shop_prompt.context_anchor_enrichment.template');
    }
    public function isShopQueryRagAnchorEnrichmentEnabled(): bool
    {
        return $this->getRequiredBool('shop_prompt.rag_anchor_enrichment.enabled');
    }

    public function getShopQueryRagAnchorEnrichmentMinScore(): int
    {
        return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.min_score');
    }

    public function getShopQueryRagAnchorEnrichmentMaxQueryTerms(): int
    {
        return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.max_query_terms');
    }

    public function getShopQueryRagAnchorEnrichmentEarlyChunkBonusMax(): int
    {
        return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.early_chunk_bonus_max');
    }

    public function getShopQueryRagAnchorEnrichmentExactValueUnitScore(): int
    {
        return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.scores.exact_value_with_unit');
    }

    public function getShopQueryRagAnchorEnrichmentExactValueScore(): int
    {
        return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.scores.exact_value_only');
    }

    public function getShopQueryRagAnchorEnrichmentAnchorBonusScore(): int
    {
        return $this->getRequiredInt('shop_prompt.rag_anchor_enrichment.scores.anchor_bonus');
    }

    public function getShopQueryRagAnchorEnrichmentTemplate(): string
    {
        return $this->getRequiredString('shop_prompt.rag_anchor_enrichment.template');
    }

    /**
     * @return string[]
     */
    public function getShopQueryRagAnchorEnrichmentNumericFocusPatterns(): array
    {
        return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.numeric_focus_patterns');
    }

    /**
     * @return string[]
     */
    public function getShopQueryRagAnchorEnrichmentProductTitlePatterns(): array
    {
        return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.product_title_patterns');
    }

    /**
     * @return string[]
     */
    public function getShopQueryRagAnchorEnrichmentAnchorBonusPatterns(): array
    {
        return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.anchor_bonus_patterns');
    }

    /**
     * @return string[]
     */
    public function getShopQueryRagAnchorEnrichmentSubjectTerms(): array
    {
        return $this->getRequiredStringList('shop_prompt.rag_anchor_enrichment.subject_terms');
    }

    public function getShopQueryTranslationReplacements(string $language): array
    {
        $value = $this->getOptionalStringMap('shop_prompt.language_preservation.translation_replacements.' . $language);
        $out = [];

        foreach ($value as $source => $target) {
            $source = strtolower(trim($source));
            $target = trim($target);

            if ($source !== '' && $target !== '') {
                $out[$source] = $target;
            }
        }

        uksort($out, static fn(string $a, string $b): int => strlen($b) <=> strlen($a));

        return $out;
    }

    private function buildRulesBlock(array $rules, string $headline = 'Rules:'): string
    {
        return $headline . "\n" . implode("\n", $rules);
    }

    /**
     * @param string[] $blocks
     */
    private function implodePromptBlocks(array $blocks): string
    {
        $normalized = array_values(array_filter(
            array_map(
                static fn(string $block): string => trim($block),
                $blocks
            ),
            static fn(string $block): bool => $block !== ''
        ));

        return implode("\n\n", $normalized);
    }
}
