package com.infoepoch.pms.agent.domain.care.understanding;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.infoepoch.pms.agent.common.utils.LogHelper;
import com.infoepoch.pms.agent.config.JsonUtils;
import com.infoepoch.pms.agent.domain.care.model.ActivityMatchUserRule;
import com.infoepoch.pms.agent.domain.care.log.CareTraceLogSupport;
import com.infoepoch.pms.agent.domain.care.model.CareConversationState;
import com.infoepoch.pms.agent.domain.care.model.CareQuery;
import com.infoepoch.pms.agent.domain.care.model.CareTaskType;
import com.infoepoch.pms.agent.observability.ai.AiComponentType;
import com.infoepoch.pms.agent.observability.ai.AiModelInvoker;
import com.infoepoch.pms.agent.observability.ai.AiObservationContext;
import com.infoepoch.pms.agent.observability.ai.AiObservationSupport;
import com.infoepoch.pms.agent.properties.CareAgentProperties;
import com.infoepoch.pms.agent.tool.union.care.provider.CareBusinessDataProvider;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自然语言理解
 */
@Service
public class CareQueryUnderstandingService {

    private static final String ACTIVITY_SUFFIX = "活动";
    private static final List<Pattern> EXPLICIT_COUNT_PATTERNS = List.of(
            Pattern.compile("(?:推荐|来|给我|帮我|请|麻烦)(?:[^\\d]{0,8}?)(\\d{1,4})\\s*(个|位|条|名|人)"),
            Pattern.compile("(\\d{1,4})\\s*(个|位|条|名|人)(?:活动|用户|推荐)")
    );
    private final ChatModel chatModel;
    private final CareAgentProperties properties;
    private final CareBusinessDataProvider careBusinessDataProvider;
    private final AiModelInvoker aiModelInvoker;
    private final AiObservationSupport observationSupport;

    public CareQueryUnderstandingService(@Qualifier("SiliconFlowChatModel") ChatModel chatModel,
                                         CareAgentProperties properties,
                                         CareBusinessDataProvider careBusinessDataProvider,
                                         AiModelInvoker aiModelInvoker,
                                         AiObservationSupport observationSupport) {
        this.chatModel = chatModel;
        this.properties = properties;
        this.careBusinessDataProvider = careBusinessDataProvider;
        this.aiModelInvoker = aiModelInvoker;
        this.observationSupport = observationSupport;
    }

    /**
     * 将用户自然语言转换成统一的推荐查询对象。
     */
    public CareQuery understand(String traceId, String sessionId, String message, CareConversationState state) {
        //去除首尾空格
        String normalizedMessage = normalize(message);
        //提取用户输入中的数量 例如请帮我推荐400个用户参加健步走活动->400
        int requestedCount = resolveRequestedCount(normalizedMessage, state);
        //判断是否是轮续请求 再来一些|继续
        boolean continuation = isContinuation(normalizedMessage);
        LogHelper.info(this, CareTraceLogSupport.format(
                traceId,
                sessionId,
                "意图识别开始",
                "输入摘要=%s，存在会话状态=%s，是否续轮=%s".formatted(
                        CareTraceLogSupport.safeMessageSummary(normalizedMessage),
                        state != null,
                        continuation
                )
        ));

        if (isRecommendationContinuation(continuation, state)
                && !hasSubstantiveContentAfterContinuation(normalizedMessage)) {
            CareQuery query = new CareQuery(
                    state.taskType(),
                    requestedCount,
                    true,
                    normalizedMessage,
                    state.activityIntent(),
                    state.preferenceSummary(),
                    state.searchConditions(),
                    ""
            );
            logIntentResult(traceId, sessionId, "沿用会话状态", query);
            return query;
        }

        CareTaskType taskType = resolveTaskType(traceId, sessionId, normalizedMessage, state, continuation);
        if (taskType == CareTaskType.GENERAL_CHAT) {
            CareQuery query = new CareQuery(
                    taskType,
                    requestedCount,
                    false,
                    normalizedMessage,
                    "",
                    normalizedMessage,
                    Collections.emptyMap(),
                    ""
            );
            logIntentResult(traceId, sessionId, "模型判定", query);
            return query;
        }
        //意图识别请求
        ModelUnderstandingResult modelResult = callModel(traceId, sessionId, normalizedMessage, taskType, state);
        boolean canReuseStateUnderstanding = canReuseStateUnderstanding(state, taskType);
        String activityIntent = StringUtils.hasText(modelResult.activityIntent())
                ? modelResult.activityIntent().trim()
                : canReuseStateUnderstanding ? state.activityIntent() : "";
        String preferenceSummary = StringUtils.hasText(modelResult.preferenceSummary())
                ? modelResult.preferenceSummary().trim()
                : normalizedMessage;
        Map<String, Object> searchConditions = mergeConditions(
                state,
                taskType,
                normalizeSearchConditions(modelResult.searchConditions()),
                continuation
        );
        if (taskType == CareTaskType.RECOMMEND_USERS) {
            searchConditions = enrichRecommendUserConditions(traceId, sessionId, activityIntent, searchConditions);
        }

        CareQuery query = new CareQuery(
                taskType,
                requestedCount,
                continuation,
                normalizedMessage,
                activityIntent,
                preferenceSummary,
                searchConditions,
                ""
        );
        logIntentResult(traceId, sessionId, "模型理解", query);
        return query;
    }

    /**
     * 根据活动语义匹配规则，补全推荐用户检索条件。
     */
    private Map<String, Object> enrichRecommendUserConditions(String traceId,
                                                              String sessionId,
                                                              String activityIntent,
                                                              Map<String, Object> searchConditions) {
        if (!StringUtils.hasText(activityIntent)) {
            return searchConditions;
        }
        try {
            List<ActivityMatchUserRule> rules = careBusinessDataProvider.listActivityMatchUserRules(traceId, sessionId);
            ActivityMatchUserRule matchedRule = selectBestMatchingRule(activityIntent, rules);
            if (matchedRule == null) {
                return searchConditions;
            }
            LinkedHashMap<String, Object> merged = new LinkedHashMap<>(searchConditions);
            putIfAbsent(merged, "sex", normalizeSex(matchedRule.sex()));
            putIfAbsent(merged, "minAge", matchedRule.minAge());
            putIfAbsent(merged, "maxAge", matchedRule.maxAge());
            mergeCsvCondition(merged, "bmi", matchedRule.bmi());
            mergeCsvCondition(merged, "hobby", matchedRule.hobby());
            mergeCsvCondition(merged, "specialty", matchedRule.specialty());
            LogHelper.info(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "活动规则补全完成",
                    "活动语义=%s，命中规则=%s，补全后条件=%s".formatted(
                            CareTraceLogSupport.safeText(activityIntent),
                            CareTraceLogSupport.safeText(matchedRule.activityKey()),
                            CareTraceLogSupport.safeConditionsSummary(merged)
                    )
            ));
            return merged;
        } catch (RuntimeException exception) {
            LogHelper.error(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "活动规则补全失败",
                    "活动语义=%s，降级为模型条件，异常=%s".formatted(
                            CareTraceLogSupport.safeText(activityIntent),
                            CareTraceLogSupport.safeText(exception.getMessage())
                    )
            ), exception);
            return searchConditions;
        }
    }

    /**
     * 清理消息首尾空白，保持后续处理稳定。
     */
    private String normalize(String message) {
        return message == null ? "" : message.trim();
    }

    /**
     * 从自然语言中提取目标数量，提取不到时走默认值或续轮值。
     */
    private int resolveRequestedCount(String message, CareConversationState state) {
        for (Pattern pattern : EXPLICIT_COUNT_PATTERNS) {
            Matcher matcher = pattern.matcher(message);
            while (matcher.find()) {
                int value = Integer.parseInt(matcher.group(1));
                if (value > 0) {
                    return Math.min(value, properties.getMaxRequestedCount());
                }
            }
        }
        if (state != null && isContinuation(message) && state.taskType().isRecommendation()) {
            return state.requestedCount();
        }
        return properties.getDefaultCount();
    }

    /**
     * 判断当前消息是否属于续轮请求。
     */
    private boolean isContinuation(String message) {
        return message.contains("再来一些")
                || message.contains("继续")
                || message.contains("换一批")
                || message.contains("更多");
    }

    /**
     * 判断当前消息是否可沿用推荐链路继续执行。
     */
    private boolean isRecommendationContinuation(boolean continuation, CareConversationState state) {
        return continuation && state != null && state.taskType().isRecommendation();
    }

    /**
     * 判断续轮消息里是否携带了新的筛选信息。
     */
    private boolean hasSubstantiveContentAfterContinuation(String message) {
        String stripped = message
                .replace("再来一些", "")
                .replace("继续", "")
                .replace("换一批", "")
                .replace("更多", "")
                .trim();
        return StringUtils.hasText(stripped);
    }

    /**
     * 续轮场景沿用状态，其余场景统一交给模型分类。
     */
    private CareTaskType resolveTaskType(String traceId,
                                         String sessionId,
                                         String message,
                                         CareConversationState state,
                                         boolean continuation) {
        if (isRecommendationContinuation(continuation, state)
                && !hasSubstantiveContentAfterContinuation(message)) {
            LogHelper.info(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "意图识别结果",
                    "识别方式=沿用会话状态，任务类型=" + toChineseTaskType(state.taskType())
            ));
            return state.taskType();
        }
        String classificationPrompt = """
                你是任务分类器。请根据用户的话，判断属于以下哪一种：
                1. RECOMMEND_ACTIVITIES：用户想让你推荐活动
                2. RECOMMEND_USERS：用户想让你推荐适合活动的用户
                3. GENERAL_CHAT：其他需要直接对话回复的内容

                只能输出一个英文枚举值，不要输出其他内容：
                RECOMMEND_ACTIVITIES
                RECOMMEND_USERS
                GENERAL_CHAT

                用户输入：%s
                """.formatted(message);
        try {
            String modelDecision = aiModelInvoker.call(
                    buildModelContext(traceId, sessionId, "任务分类", "care.intentClassifier"),
                    classificationPrompt,
                    chatModel
            );
            String normalizedDecision = modelDecision == null ? "" : modelDecision.trim();
            LogHelper.info(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "意图识别结果",
                    "识别方式=模型判定，模型原始结果=" + CareTraceLogSupport.safeText(normalizedDecision)
            ));
            if ("RECOMMEND_USERS".equalsIgnoreCase(normalizedDecision)) {
                return CareTaskType.RECOMMEND_USERS;
            }
            if ("RECOMMEND_ACTIVITIES".equalsIgnoreCase(normalizedDecision)) {
                return CareTaskType.RECOMMEND_ACTIVITIES;
            }
        } catch (RuntimeException exception) {
            LogHelper.info(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "任务分类模型降级",
                    "分类失败，降级为通用对话"
            ));
        }
        return CareTaskType.GENERAL_CHAT;
    }

    /**
     * 让模型产出结构化理解结果，并在失败时退回保守兜底。
     */
    private ModelUnderstandingResult callModel(String traceId,
                                               String sessionId,
                                               String message,
                                               CareTaskType taskType,
                                               CareConversationState state) {
        String stateSummary = buildStateSummary(state);
        String understandingPrompt = """
                你是精准关爱系统的请求理解助手。请把用户自然语言整理成JSON，不能输出任何JSON以外的内容。
                taskType 只能是 RECOMMEND_ACTIVITIES 或 RECOMMEND_USERS。
                activityIntent 填活动名称、活动主题或活动语义摘要；推荐活动场景可以留空。
                preferenceSummary 填用户偏好和筛选要求的简述。
                searchConditions 是一个对象，只能使用以下字段：sex、minAge、maxAge、bmi、hobby、specialty、regionName、departmentName。
                sex 使用原始编码：男传 0，女传 1。
                bmi、hobby、specialty 如有多个值，用英文逗号拼接成字符串。

                当前任务倾向：%s
                上下文状态：%s
                用户输入：%s

                输出格式：
                {
                  "taskType": "RECOMMEND_ACTIVITIES",
                  "activityIntent": "",
                  "preferenceSummary": "",
                  "searchConditions": {}
                }
                """.formatted(taskType.name(), stateSummary, message);
        try {
            String response = aiModelInvoker.call(
                    buildModelContext(traceId, sessionId, "意图理解", "care.queryUnderstanding"),
                    understandingPrompt,
                    chatModel
            );
            String sanitized = sanitizeJson(response);
            if (!StringUtils.hasText(sanitized)) {
                LogHelper.info(this, CareTraceLogSupport.format(
                        traceId,
                        sessionId,
                        "意图识别模型返回为空",
                        "模型未返回可解析JSON"
                ));
                return fallbackUnderstanding(taskType, message, state);
            }
            ModelUnderstandingResult result = JsonUtils.jsonToObject(sanitized, ModelUnderstandingResult.class);
            if (result == null) {
                return fallbackUnderstanding(taskType, message, state);
            }
            return result;
        } catch (RuntimeException exception) {
            LogHelper.info(this, CareTraceLogSupport.format(
                    traceId,
                    sessionId,
                    "意图识别模型降级",
                    "模型理解失败，回退保守结果"
            ));
            return fallbackUnderstanding(taskType, message, state);
        }
    }

    private AiObservationContext buildModelContext(String traceId, String sessionId, String scene, String componentName) {
        AiObservationContext currentContext = observationSupport.currentContext();
        String requestId = StringUtils.hasText(currentContext.requestId()) ? currentContext.requestId() : traceId;
        String flowId = StringUtils.hasText(currentContext.flowId()) ? currentContext.flowId() : requestId;
        return currentContext.toBuilder()
                .traceId(traceId)
                .requestId(requestId)
                .sessionId(sessionId)
                .flowId(flowId)
                .scene(scene)
                .componentType(AiComponentType.MODEL)
                .componentName(componentName)
                .modelProvider("siliconflow")
                .modelName(chatModel.getClass().getSimpleName())
                .build();
    }

    /**
     * 构造供模型参考的短会话摘要。
     */
    private String buildStateSummary(CareConversationState state) {
        if (state == null) {
            return "无";
        }
        return """
                taskType=%s, requestedCount=%s, activityIntent=%s, preferenceSummary=%s
                """.formatted(
                state.taskType(),
                state.requestedCount(),
                state.activityIntent(),
                state.preferenceSummary()
        ).trim();
    }

    /**
     * 当模型理解失败时，退回到最保守的理解结果。
     */
    private ModelUnderstandingResult fallbackUnderstanding(CareTaskType taskType, String message, CareConversationState state) {
        String activityIntent = taskType == CareTaskType.RECOMMEND_USERS ? message : "";
        if (canReuseStateUnderstanding(state, taskType) && isContinuation(message)) {
            activityIntent = state.activityIntent();
        }
        return new ModelUnderstandingResult(
                taskType.name(),
                activityIntent,
                message,
                Collections.emptyMap()
        );
    }

    /**
     * 将续轮条件与本轮模型结果合并成最终查询条件。
     */
    private Map<String, Object> mergeConditions(CareConversationState state,
                                                CareTaskType taskType,
                                                Map<String, Object> modelConditions,
                                                boolean continuation) {
        LinkedHashMap<String, Object> merged = new LinkedHashMap<>();
        if (continuation && canReuseStateUnderstanding(state, taskType)) {
            merged.putAll(Optional.ofNullable(state.searchConditions()).orElse(Collections.emptyMap()));
        }
        if (modelConditions != null) {
            merged.putAll(modelConditions);
        }
        return merged;
    }

    /**
     * 仅在续轮仍处于同一推荐任务类型时沿用旧状态理解结果。
     */
    private boolean canReuseStateUnderstanding(CareConversationState state, CareTaskType taskType) {
        return state != null && state.taskType() == taskType && taskType != null && taskType.isRecommendation();
    }

    /**
     * 将模型输出和兼容字段收敛为接口文档允许的查询条件。
     */
    private Map<String, Object> normalizeSearchConditions(Map<String, Object> conditions) {
        if (conditions == null || conditions.isEmpty()) {
            return Collections.emptyMap();
        }
        LinkedHashMap<String, Object> normalized = new LinkedHashMap<>();
        copyStringValue(conditions, normalized, "sex", "sex", "gender");
        copyIntegerValue(conditions, normalized, "minAge", "minAge", "ageMin");
        copyIntegerValue(conditions, normalized, "maxAge", "maxAge", "ageMax");
        copyCsvValue(conditions, normalized, "bmi", "bmi");
        copyCsvValue(conditions, normalized, "hobby", "hobby", "interests", "interest");
        copyCsvValue(conditions, normalized, "specialty", "specialty", "specialties", "talents", "tags");
        copyStringValue(conditions, normalized, "regionName", "regionName", "region", "area", "city");
        copyStringValue(conditions, normalized, "departmentName", "departmentName", "department", "org");

        String normalizedSex = normalizeSex(normalized.get("sex"));
        if (StringUtils.hasText(normalizedSex)) {
            normalized.put("sex", normalizedSex);
        } else {
            normalized.remove("sex");
        }
        return normalized;
    }

    /**
     * 从规则列表里选择最匹配当前活动语义的一条规则。
     */
    private ActivityMatchUserRule selectBestMatchingRule(String activityIntent, List<ActivityMatchUserRule> rules) {
        if (!StringUtils.hasText(activityIntent) || rules == null || rules.isEmpty()) {
            return null;
        }
        String normalizedIntent = normalizeActivityKeyword(activityIntent);
        List<ActivityMatchUserRule> exactMatches = rules.stream()
                .filter(rule -> normalizedIntent.equals(normalizeActivityKeyword(rule.activityKey())))
                .toList();
        if (!exactMatches.isEmpty()) {
            return longestKeywordRule(exactMatches);
        }
        List<ActivityMatchUserRule> fuzzyMatches = rules.stream()
                .filter(rule -> {
                    String normalizedKey = normalizeActivityKeyword(rule.activityKey());
                    return StringUtils.hasText(normalizedKey)
                            && (normalizedIntent.contains(normalizedKey) || normalizedKey.contains(normalizedIntent));
                })
                .toList();
        if (fuzzyMatches.isEmpty()) {
            return null;
        }
        return longestKeywordRule(fuzzyMatches);
    }

    /**
     * 选择关键词最长的一条规则，降低短词误命中的概率。
     */
    private ActivityMatchUserRule longestKeywordRule(List<ActivityMatchUserRule> rules) {
        ActivityMatchUserRule bestRule = null;
        int bestLength = -1;
        for (ActivityMatchUserRule rule : rules) {
            int currentLength = normalizeActivityKeyword(rule.activityKey()).length();
            if (currentLength > bestLength) {
                bestRule = rule;
                bestLength = currentLength;
            }
        }
        return bestRule;
    }

    /**
     * 记录最终意图识别结果。
     */
    private void logIntentResult(String traceId, String sessionId, String resolvedBy, CareQuery query) {
        LogHelper.info(this, CareTraceLogSupport.format(
                traceId,
                sessionId,
                "意图识别结果",
                "识别方式=%s，任务类型=%s，目标数量=%s，是否续轮=%s，筛选条件=%s".formatted(
                        resolvedBy,
                        toChineseTaskType(query.taskType()),
                        query.requestedCount(),
                        query.continuation(),
                        CareTraceLogSupport.safeConditionsSummary(query.searchConditions())
                )
        ));
    }

    /**
     * 复制单值字符串条件。
     */
    private void copyStringValue(Map<String, Object> source, Map<String, Object> target, String targetKey, String... aliases) {
        for (String alias : aliases) {
            Object value = source.get(alias);
            if (value == null) {
                continue;
            }
            String text = stringify(value);
            if (StringUtils.hasText(text)) {
                target.put(targetKey, text);
                return;
            }
        }
    }

    /**
     * 复制整数条件。
     */
    private void copyIntegerValue(Map<String, Object> source, Map<String, Object> target, String targetKey, String... aliases) {
        for (String alias : aliases) {
            Object value = source.get(alias);
            Integer number = toInteger(value);
            if (number != null) {
                target.put(targetKey, number);
                return;
            }
        }
    }

    /**
     * 将数组或多值字符串转成上游接口约定的英文逗号分隔字符串。
     */
    private void copyCsvValue(Map<String, Object> source, Map<String, Object> target, String targetKey, String... aliases) {
        for (String alias : aliases) {
            Object value = source.get(alias);
            String csv = toCsv(value);
            if (StringUtils.hasText(csv)) {
                target.put(targetKey, csv);
                return;
            }
        }
    }

    /**
     * 统一性别编码，保证发给上游的是原始编码值。
     */
    private String normalizeSex(Object value) {
        String text = stringify(value).toLowerCase(Locale.ROOT);
        if (!StringUtils.hasText(text)) {
            return "";
        }
        if ("0".equals(text) || "男".equals(text) || "男性".equals(text) || "male".equals(text)) {
            return "0";
        }
        if ("1".equals(text) || "女".equals(text) || "女性".equals(text) || "female".equals(text)) {
            return "1";
        }
        return "";
    }

    /**
     * 统一活动关键词，去掉常见后缀和分隔符，便于命中规则。
     */
    private String normalizeActivityKeyword(String value) {
        String normalized = stringify(value)
                .replace(ACTIVITY_SUFFIX, "")
                .replace(" ", "")
                .replace("　", "")
                .replace("（", "")
                .replace("）", "")
                .replace("(", "")
                .replace(")", "")
                .replace("、", "")
                .replace("-", "")
                .replace("_", "");
        return normalized;
    }

    /**
     * 将对象转换为整数。
     */
    private Integer toInteger(Object value) {
        if (value instanceof Number number) {
            return number.intValue();
        }
        String text = stringify(value);
        if (!StringUtils.hasText(text)) {
            return null;
        }
        try {
            return Integer.parseInt(text);
        } catch (NumberFormatException exception) {
            return null;
        }
    }

    /**
     * 将对象转换为逗号分隔字符串。
     */
    private String toCsv(Object value) {
        if (value == null) {
            return "";
        }
        if (value instanceof Iterable<?> iterable) {
            List<String> parts = new ArrayList<>();
            for (Object item : iterable) {
                String text = stringify(item);
                if (StringUtils.hasText(text)) {
                    parts.add(text);
                }
            }
            return String.join(",", parts);
        }
        String text = stringify(value);
        if (!StringUtils.hasText(text)) {
            return "";
        }
        return text.replace("，", ",");
    }

    /**
     * 统一规则中的逗号分隔字符串格式。
     */
    private String normalizeCsv(String value) {
        return StringUtils.hasText(value) ? value.replace("，", ",").trim() : "";
    }

    /**
     * 将任意对象转成字符串。
     */
    private String stringify(Object value) {
        return value == null ? "" : value.toString().trim();
    }

    /**
     * 仅当目标字段为空时再补充规则值，保证用户显式条件优先。
     */
    private void putIfAbsent(Map<String, Object> target, String key, Object value) {
        if (target.containsKey(key) || value == null) {
            return;
        }
        if (value instanceof String text && !StringUtils.hasText(text)) {
            return;
        }
        target.put(key, value);
    }

    /**
     * 合并逗号分隔的多值条件，保留用户原始顺序并对规则补充值去重。
     */
    private void mergeCsvCondition(Map<String, Object> target, String key, String ruleValue) {
        String normalizedRuleValue = normalizeCsv(ruleValue);
        if (!StringUtils.hasText(normalizedRuleValue)) {
            return;
        }
        if (!target.containsKey(key)) {
            target.put(key, normalizedRuleValue);
            return;
        }
        String existingValue = toCsv(target.get(key));
        if (!StringUtils.hasText(existingValue)) {
            target.put(key, normalizedRuleValue);
            return;
        }
        target.put(key, mergeCsvValues(existingValue, normalizedRuleValue));
    }

    /**
     * 将用户条件与规则条件按顺序去重合并。
     */
    private String mergeCsvValues(String userValue, String ruleValue) {
        Set<String> merged = new LinkedHashSet<>();
        appendCsvValues(merged, userValue);
        appendCsvValues(merged, ruleValue);
        return String.join(",", merged);
    }

    /**
     * 拆分并追加逗号分隔的条件值。
     */
    private void appendCsvValues(Set<String> target, String value) {
        String normalized = normalizeCsv(value);
        if (!StringUtils.hasText(normalized)) {
            return;
        }
        for (String part : normalized.split(",")) {
            String trimmed = part.trim();
            if (StringUtils.hasText(trimmed)) {
                target.add(trimmed);
            }
        }
    }

    /**
     * 从模型回复中截取可解析的 JSON 片段。
     */
    private String sanitizeJson(String text) {
        if (!StringUtils.hasText(text)) {
            return "";
        }
        String normalized = text.trim()
                .replace("```json", "")
                .replace("```", "")
                .trim();
        int start = normalized.indexOf('{');
        int end = normalized.lastIndexOf('}');
        if (start >= 0 && end > start) {
            return normalized.substring(start, end + 1);
        }
        return "";
    }

    /**
     * 将任务类型转换成中文展示。
     */
    private String toChineseTaskType(CareTaskType taskType) {
        if (taskType == null) {
            return "未知";
        }
        return switch (taskType) {
            case RECOMMEND_ACTIVITIES -> "推荐活动";
            case RECOMMEND_USERS -> "推荐用户";
            case GENERAL_CHAT -> "通用对话";
        };
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    private record ModelUnderstandingResult(
            String taskType,
            String activityIntent,
            String preferenceSummary,
            Map<String, Object> searchConditions
    ) {
    }
}
