package com.ruoyi.generator.service.front;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.config.DeepSeekProperties;
import com.ruoyi.generator.domain.front.FrontProject;
import com.ruoyi.generator.domain.front.FrontProjectColumn;
import com.ruoyi.generator.domain.front.FrontProjectGeneration;
import com.ruoyi.generator.domain.front.FrontProjectTable;
import com.ruoyi.generator.domain.front.dto.AppBlueprintDesign;
import com.ruoyi.generator.domain.front.dto.AppMenuDesign;
import com.ruoyi.generator.domain.front.dto.AppRoleDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionEffectDesign;
import com.ruoyi.generator.domain.front.dto.BusinessActionRuleDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseColumnDesign;
import com.ruoyi.generator.domain.front.dto.DatabaseDesignResponse;
import com.ruoyi.generator.domain.front.dto.DatabaseTableDesign;
import com.ruoyi.generator.domain.front.dto.GenerateAppBlueprintRequest;
import com.ruoyi.generator.domain.front.dto.GenerateBusinessBlueprintRequest;
import com.ruoyi.generator.domain.front.dto.GenerateDatabaseRequest;
import com.ruoyi.generator.mapper.front.FrontProjectColumnMapper;
import com.ruoyi.generator.mapper.front.FrontProjectMapper;
import com.ruoyi.generator.mapper.front.FrontProjectTableMapper;

@Service
public class AiGenerateServiceImpl implements IAiGenerateService
{
    private static final int MAX_ERROR_MESSAGE_LENGTH = 1000;
    private static final String DATA_SCOPE_PUBLIC = "PUBLIC";
    private static final String DATA_SCOPE_CURRENT_USER = "CURRENT_USER";
    private static final String DATA_SCOPE_ROLE = "ROLE";

    @Autowired
    private IDeepSeekClient deepSeekClient;
    @Autowired
    private DeepSeekProperties deepSeekProperties;
    @Autowired
    private AiDatabaseSchemaValidator validator;
    @Autowired
    private AiBusinessBlueprintValidator businessBlueprintValidator;
    @Autowired
    private MysqlDdlBuilder ddlBuilder;
    @Autowired
    private FrontProjectMapper frontProjectMapper;
    @Autowired
    private FrontProjectTableMapper frontProjectTableMapper;
    @Autowired
    private FrontProjectColumnMapper frontProjectColumnMapper;
    @Autowired
    private AiGenerationRecordService generationRecordService;

    @Override
    @Transactional
    public AppBlueprintDesign generateAppBlueprint(Long userId, Long projectId, GenerateAppBlueprintRequest request)
    {
        assertFrontUserLoggedIn(userId);
        FrontProject project = getProject(userId, projectId);
        String prompt = buildAppBlueprintPrompt(project, request);
        long start = System.currentTimeMillis();
        FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "app_blueprint");
        try
        {
            String aiContent = deepSeekClient.chat(prompt);
            AppBlueprintDesign blueprint = parseAppBlueprintResponse(aiContent);
            normalizeAppBlueprint(blueprint);
            markAppBlueprintGenerated(projectId, blueprint, request);
            generation.setResponsePayload(aiContent);
            generation.setSuccess("1");
            generation.setElapsedMs(System.currentTimeMillis() - start);
            generationRecordService.insert(generation);
            return blueprint;
        }
        catch (RuntimeException e)
        {
            generation.setSuccess("0");
            generation.setErrorMessage(sanitizeErrorMessage(e.getMessage()));
            generation.setElapsedMs(System.currentTimeMillis() - start);
            generationRecordService.insert(generation);
            throw e;
        }
    }

    @Override
    @Transactional
    public DatabaseDesignResponse generateDatabase(Long userId, Long projectId, GenerateDatabaseRequest request)
    {
        assertFrontUserLoggedIn(userId);
        FrontProject project = getProject(userId, projectId);
        String prompt = buildPrompt(project, request);
        long start = System.currentTimeMillis();
        FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "database");
        try
        {
            String aiContent = deepSeekClient.chat(prompt);
            DatabaseDesignResponse response = parseResponse(aiContent);
            response.setProjectId(projectId);
            normalizeAndBuildSql(response);
            response.setBusinessActions(new ArrayList<BusinessActionDesign>());
            validator.validate(response, maxTables(), maxColumnsPerTable());
            persistDatabase(userId, projectId, response);
            markDatabaseGenerated(projectId, request);
            generation.setResponsePayload(aiContent);
            generation.setSuccess("1");
            generation.setElapsedMs(System.currentTimeMillis() - start);
            generationRecordService.insert(generation);
            return response;
        }
        catch (RuntimeException e)
        {
            generation.setSuccess("0");
            generation.setErrorMessage(sanitizeErrorMessage(e.getMessage()));
            generation.setElapsedMs(System.currentTimeMillis() - start);
            generationRecordService.insert(generation);
            throw e;
        }
    }

    @Override
    @Transactional
    public List<BusinessActionDesign> generateBusinessBlueprint(Long userId, Long projectId, GenerateBusinessBlueprintRequest request)
    {
        assertFrontUserLoggedIn(userId);
        FrontProject project = getProject(userId, projectId);
        DatabaseDesignResponse database = loadSavedDatabaseDesign(projectId);
        String prompt = buildBusinessBlueprintPrompt(project, request, database);
        long start = System.currentTimeMillis();
        FrontProjectGeneration generation = newGeneration(projectId, userId, prompt, "business_blueprint");
        try
        {
            String aiContent = deepSeekClient.chat(prompt);
            DatabaseDesignResponse response = parseBusinessBlueprintResponse(aiContent, database);
            normalizeBusinessActions(response);
            businessBlueprintValidator.validate(response);
            markBusinessBlueprintGenerated(projectId, response.getBusinessActions(), request);
            generation.setResponsePayload(aiContent);
            generation.setSuccess("1");
            generation.setElapsedMs(System.currentTimeMillis() - start);
            generationRecordService.insert(generation);
            return response.getBusinessActions();
        }
        catch (RuntimeException e)
        {
            generation.setSuccess("0");
            generation.setErrorMessage(sanitizeErrorMessage(e.getMessage()));
            generation.setElapsedMs(System.currentTimeMillis() - start);
            generationRecordService.insert(generation);
            throw e;
        }
    }

    private void assertFrontUserLoggedIn(Long userId)
    {
        if (userId == null)
        {
            throw new ServiceException("前台用户未登录");
        }
    }

    private FrontProject getProject(Long userId, Long projectId)
    {
        FrontProject project = frontProjectMapper.selectFrontProjectByUserAndId(userId, projectId);
        if (project == null)
        {
            throw new ServiceException("项目不存在或无权限");
        }
        return project;
    }

    private String buildAppBlueprintPrompt(FrontProject project, GenerateAppBlueprintRequest request)
    {
        StringBuilder prompt = new StringBuilder();
        prompt.append("Generate an application blueprint for the target generated project. Return JSON only in this shape: ");
        prompt.append("{\"roles\":[{\"code\":\"reader\",\"name\":\"Reader\",\"description\":\"\"}],");
        prompt.append("\"frontendMenus\":[{\"code\":\"book_catalog\",\"name\":\"Book Catalog\",\"path\":\"/books\",\"requiresLogin\":false,\"visibleRoles\":[],\"dataScope\":\"PUBLIC\"}],");
        prompt.append("\"adminMenus\":[{\"code\":\"book_admin\",\"name\":\"Book Admin\",\"path\":\"/admin/books\",\"requiresLogin\":true,\"visibleRoles\":[\"admin\"],\"dataScope\":\"ROLE\"}]}.\n");
        prompt.append("This blueprint is for the generated business project, not for the current EasyCode/RuoYi platform.\n");
        prompt.append("Do not generate database tables, menu tables, role tables, permission tables, or code. Only return static role and menu configuration.\n");
        prompt.append("role.name must be Simplified Chinese so the blueprint summary can display Chinese role names. Keep role.code in lowercase English snake_case.\n");
        prompt.append("Every menu must include requiresLogin, visibleRoles, and dataScope. dataScope must be PUBLIC, CURRENT_USER, or ROLE.\n");
        prompt.append("If a menu is for personal data such as my orders, my borrows, profile, reservations, or payments, use requiresLogin=true and dataScope=CURRENT_USER.\n");
        prompt.append("Project name: ").append(firstNonBlank(request == null ? null : request.getProjectName(), project.getProjectName())).append("\n");
        prompt.append("Project description: ").append(firstNonBlank(request == null ? null : request.getProjectDesc(), project.getProjectDesc())).append("\n");
        prompt.append("Industry template: ").append(firstNonBlank(request == null ? null : request.getIndustryTemplate(), project.getIndustryTemplate())).append("\n");
        prompt.append("Extra requirements: ").append(request == null ? "" : StringUtils.defaultString(request.getExtraRequirements())).append("\n");
        return prompt.toString();
    }

    private String buildPrompt(FrontProject project, GenerateDatabaseRequest request)
    {
        StringBuilder prompt = new StringBuilder();
        appendAppBlueprintConstraints(prompt, project, request);
        prompt.append("Generate a MySQL database design for an enterprise management system. Return JSON only in this shape: ");
        prompt.append("{\"tables\":[{\"tableName\":\"car_info\",\"tableComment\":\"Car info table\",\"columns\":[]}]}.\n");
        prompt.append("Do not generate businessActions, business blueprint, workflows, code, menu tables, role tables, or permission tables in this step.\n");
        prompt.append("限制：表名和字段名使用小写字母、数字、下划线；必须且只能有一个主键；字段类型只能使用bigint(20)、int(11)、varchar(n)、char(n)、decimal(p,s)、datetime、date、text、longtext。\n");
        prompt.append("项目名称：").append(firstNonBlank(request == null ? null : request.getProjectName(), project.getProjectName())).append("\n");
        prompt.append("项目描述：").append(firstNonBlank(request == null ? null : request.getProjectDesc(), project.getProjectDesc())).append("\n");
        prompt.append("行业模板：").append(firstNonBlank(request == null ? null : request.getIndustryTemplate(), project.getIndustryTemplate())).append("\n");
        prompt.append("补充要求：").append(request == null ? "" : StringUtils.defaultString(request.getExtraRequirements())).append("\n");
        prompt.append("最多生成").append(maxTables()).append("张表，每张表最多").append(maxColumnsPerTable()).append("个字段。");
        return prompt.toString();
    }

    private void appendAppBlueprintConstraints(StringBuilder prompt, FrontProject project, GenerateDatabaseRequest request)
    {
        String confirmedAppBlueprint = resolveConfirmedAppBlueprint(project, request);
        if (StringUtils.isBlank(confirmedAppBlueprint))
        {
            return;
        }
        prompt.append("Confirmed app blueprint JSON: ").append(confirmedAppBlueprint).append("\n");
        prompt.append("Database generation constraints from the confirmed app blueprint:\n");
        prompt.append("- Do not generate menu or permission tables for frontendMenus, adminMenus, roles, or visibleRoles.\n");
        prompt.append("- Menus are static generated project configuration, not database rows.\n");
        prompt.append("- Any feature behind requiresLogin=true and dataScope=CURRENT_USER must associate data with the current user, usually through a user_id field.\n");
    }

    private String resolveConfirmedAppBlueprint(FrontProject project, GenerateDatabaseRequest request)
    {
        return firstNonBlank(request == null ? null : request.getAppBlueprint(), project.getAppBlueprint());
    }

    private String buildBusinessBlueprintPrompt(FrontProject project, GenerateBusinessBlueprintRequest request, DatabaseDesignResponse database)
    {
        StringBuilder prompt = new StringBuilder();
        prompt.append("Generate a business blueprint from the saved database schema. Return JSON only in this shape: ");
        prompt.append("{\"businessActions\":[{\"code\":\"borrow_book\",\"name\":\"借书\",\"ownerTable\":\"borrow_record\",\"method\":\"POST\",\"path\":\"/library/borrow/borrow\",\"transaction\":true,\"requestFields\":[\"book_id\"],\"rules\":[\"图书必须可借\"],\"ruleChecks\":[{\"type\":\"NUMBER_GTE\",\"targetTable\":\"book_info\",\"targetField\":\"stock\",\"compareValue\":\"1\",\"conditionFields\":[\"book_id\"],\"message\":\"库存不足\"}],\"effects\":[{\"type\":\"INSERT_ROW\",\"targetTable\":\"borrow_record\",\"values\":{\"book_id\":\"${param.book_id}\",\"status\":\"borrowed\"},\"description\":\"创建借阅记录\"},{\"type\":\"DECREASE_NUMBER\",\"targetTable\":\"book_info\",\"targetField\":\"stock\",\"amount\":\"1\",\"minValue\":\"0\",\"conditionFields\":[\"book_id\"],\"message\":\"库存扣减失败\",\"description\":\"库存减一\"}]}]}.\n");
        prompt.append("Do not generate CRUD. Do not generate create, read, update, delete, list, detail, import, export, or simple table maintenance actions.\n");
        prompt.append("Generate only business workflows that go beyond CRUD, such as approval, payment confirmation, borrow, return, stock adjustment by workflow, scheduling, check-in, settlement, or status transition.\n");
        prompt.append("Do not generate database tables or code. businessActions only describes a safe DSL.\n");
        prompt.append("Supported ruleChecks are EXISTS, NOT_EXISTS, FIELD_EQUALS, FIELD_NOT_EQUALS, FIELD_IN, NUMBER_GTE.\n");
        prompt.append("Supported effects are INSERT_ROW, UPDATE_FIELDS, SET_STATUS, INCREASE_NUMBER, DECREASE_NUMBER.\n");
        prompt.append("businessActions.name, rules, ruleChecks.message, effects.message, and effects.description must be Simplified Chinese. Keep API paths and request parameter fields as technical identifiers.\n");
        prompt.append("ownerTable, requestFields, effects.targetTable, effects.targetField, and effects.conditionFields must reference the saved database schema below.\n");
        prompt.append("method must be GET, POST, PUT, or DELETE. Prefer POST for command workflows.\n");
        prompt.append("Project name: ").append(firstNonBlank(request == null ? null : request.getProjectName(), project.getProjectName())).append("\n");
        prompt.append("Project description: ").append(firstNonBlank(request == null ? null : request.getProjectDesc(), project.getProjectDesc())).append("\n");
        prompt.append("Industry template: ").append(firstNonBlank(request == null ? null : request.getIndustryTemplate(), project.getIndustryTemplate())).append("\n");
        prompt.append("Extra requirements: ").append(request == null ? "" : StringUtils.defaultString(request.getExtraRequirements())).append("\n");
        prompt.append("Saved database schema:\n");
        appendSavedDatabaseSchema(prompt, database);
        return prompt.toString();
    }

    private void appendSavedDatabaseSchema(StringBuilder prompt, DatabaseDesignResponse database)
    {
        if (database == null || database.getTables() == null)
        {
            return;
        }
        for (DatabaseTableDesign table : database.getTables())
        {
            if (table == null)
            {
                continue;
            }
            prompt.append("- ").append(table.getTableName()).append(" (")
                    .append(StringUtils.defaultString(table.getTableComment())).append(")\n");
            if (table.getColumns() == null)
            {
                continue;
            }
            for (DatabaseColumnDesign column : table.getColumns())
            {
                if (column == null)
                {
                    continue;
                }
                prompt.append("  - ").append(column.getColumnName()).append(" ")
                        .append(StringUtils.defaultString(column.getColumnType())).append(" ")
                        .append(StringUtils.defaultString(column.getColumnComment())).append("\n");
            }
        }
    }

    private String firstNonBlank(String first, String second)
    {
        return StringUtils.isNotBlank(first) ? first : StringUtils.defaultString(second);
    }

    private AppBlueprintDesign parseAppBlueprintResponse(String aiContent)
    {
        String content = stripMarkdownFence(aiContent);
        AppBlueprintDesign blueprint = JSON.parseObject(content, AppBlueprintDesign.class);
        if (blueprint == null)
        {
            throw new ServiceException("AI app blueprint is empty");
        }
        return blueprint;
    }

    private void normalizeAppBlueprint(AppBlueprintDesign blueprint)
    {
        if (blueprint.getRoles() == null)
        {
            blueprint.setRoles(new ArrayList<AppRoleDesign>());
        }
        if (blueprint.getFrontendMenus() == null)
        {
            blueprint.setFrontendMenus(new ArrayList<AppMenuDesign>());
        }
        if (blueprint.getAdminMenus() == null)
        {
            blueprint.setAdminMenus(new ArrayList<AppMenuDesign>());
        }
        Set<String> roleCodes = normalizeAppRoles(blueprint.getRoles());
        normalizeAppMenus(blueprint.getFrontendMenus(), roleCodes, "frontend_menu");
        normalizeAppMenus(blueprint.getAdminMenus(), roleCodes, "admin_menu");
    }

    private Set<String> normalizeAppRoles(List<AppRoleDesign> roles)
    {
        Set<String> usedCodes = new HashSet<String>();
        if (roles == null)
        {
            return usedCodes;
        }
        int roleIndex = 1;
        for (AppRoleDesign role : roles)
        {
            if (role == null)
            {
                continue;
            }
            String code = uniqueDatabaseName(role.getCode(), "role", roleIndex, usedCodes);
            role.setCode(code);
            role.setName(StringUtils.defaultIfEmpty(trimToLength(role.getName(), 80), code));
            role.setDescription(trimToLength(role.getDescription(), 200));
            roleIndex++;
        }
        return usedCodes;
    }

    private void normalizeAppMenus(List<AppMenuDesign> menus, Set<String> roleCodes, String fallbackPrefix)
    {
        if (menus == null)
        {
            return;
        }
        Set<String> usedCodes = new HashSet<String>();
        int menuIndex = 1;
        for (AppMenuDesign menu : menus)
        {
            if (menu == null)
            {
                continue;
            }
            String code = uniqueDatabaseName(menu.getCode(), fallbackPrefix, menuIndex, usedCodes);
            menu.setCode(code);
            menu.setName(StringUtils.defaultIfEmpty(trimToLength(menu.getName(), 80), code));
            menu.setPath(normalizeMenuPath(menu.getPath(), code));
            menu.setRequiresLogin(menu.getRequiresLogin() == null ? Boolean.FALSE : menu.getRequiresLogin());
            menu.setVisibleRoles(normalizeMenuRoles(menu.getVisibleRoles(), roleCodes));
            menu.setDataScope(normalizeDataScope(menu.getDataScope(), menu.getRequiresLogin()));
            menuIndex++;
        }
    }

    private List<String> normalizeMenuRoles(List<String> roles, Set<String> roleCodes)
    {
        List<String> normalizedRoles = new ArrayList<String>();
        if (roles == null)
        {
            return normalizedRoles;
        }
        Set<String> usedRoles = new HashSet<String>();
        for (int i = 0; i < roles.size(); i++)
        {
            String roleCode = normalizeDatabaseName(roles.get(i), "role_" + (i + 1));
            if ((roleCodes == null || roleCodes.isEmpty() || roleCodes.contains(roleCode)) && !usedRoles.contains(roleCode))
            {
                normalizedRoles.add(roleCode);
                usedRoles.add(roleCode);
            }
        }
        return normalizedRoles;
    }

    private String normalizeMenuPath(String path, String code)
    {
        String normalized = StringUtils.defaultString(path).trim();
        normalized = normalized.replaceAll("[^A-Za-z0-9_./-]+", "-").replaceAll("-+", "-");
        if (!normalized.startsWith("/"))
        {
            normalized = "/" + normalized;
        }
        normalized = normalized.replaceAll("/{2,}", "/");
        if (normalized.length() < 2)
        {
            normalized = "/" + code.replace("_", "-");
        }
        if (normalized.length() > 180)
        {
            normalized = normalized.substring(0, 180).replaceAll("[./_-]+$", "");
        }
        return normalized;
    }

    private String normalizeDataScope(String dataScope, Boolean requiresLogin)
    {
        if (!Boolean.TRUE.equals(requiresLogin))
        {
            return DATA_SCOPE_PUBLIC;
        }
        String normalized = StringUtils.defaultString(dataScope).trim().toUpperCase(Locale.ENGLISH);
        normalized = normalized.replaceAll("[^A-Z0-9]+", "_").replaceAll("_+", "_").replaceAll("^_+|_+$", "");
        if (DATA_SCOPE_PUBLIC.equals(normalized) || DATA_SCOPE_CURRENT_USER.equals(normalized) || DATA_SCOPE_ROLE.equals(normalized))
        {
            return normalized;
        }
        return DATA_SCOPE_CURRENT_USER;
    }

    private DatabaseDesignResponse parseResponse(String aiContent)
    {
        String content = stripMarkdownFence(aiContent);
        JSONObject json = JSON.parseObject(content);
        if (json == null)
        {
            throw new ServiceException("AI database design is empty");
        }
        normalizeResponseAliases(json);
        DatabaseDesignResponse response = json.toJavaObject(DatabaseDesignResponse.class);
        if (response == null)
        {
            throw new ServiceException("AI 数据库设计为空");
        }
        return response;
    }

    private DatabaseDesignResponse parseBusinessBlueprintResponse(String aiContent, DatabaseDesignResponse savedDatabase)
    {
        String content = stripMarkdownFence(aiContent);
        JSONObject json = JSON.parseObject(content);
        if (json == null)
        {
            throw new ServiceException("AI business blueprint is empty");
        }
        DatabaseDesignResponse response = new DatabaseDesignResponse();
        response.setProjectId(savedDatabase.getProjectId());
        response.setTables(savedDatabase.getTables());
        JSONArray actions = json.getJSONArray("businessActions");
        if (actions == null)
        {
            response.setBusinessActions(new ArrayList<BusinessActionDesign>());
        }
        else
        {
            response.setBusinessActions(actions.toJavaList(BusinessActionDesign.class));
        }
        return response;
    }

    private void normalizeResponseAliases(JSONObject response)
    {
        if (response == null)
        {
            return;
        }
        JSONArray tables = response.getJSONArray("tables");
        if (tables == null)
        {
            return;
        }
        for (int tableIndex = 0; tableIndex < tables.size(); tableIndex++)
        {
            JSONObject table = tables.getJSONObject(tableIndex);
            if (table == null)
            {
                continue;
            }
            JSONArray columns = table.getJSONArray("columns");
            if (columns == null)
            {
                continue;
            }
            for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++)
            {
                JSONObject column = columns.getJSONObject(columnIndex);
                if (column == null)
                {
                    continue;
                }
                normalizeFlagAlias(column, "isPk", "pk", "primaryKey", "isPrimaryKey", "primary");
                normalizeFlagAlias(column, "isIncrement", "autoIncrement", "auto_increment", "increment");
                normalizeFlagAlias(column, "isRequired", "required", "notNull", "not_null");
            }
        }
    }

    private void normalizeFlagAlias(JSONObject column, String canonicalName, String... aliases)
    {
        if (column.containsKey(canonicalName))
        {
            String normalized = normalizeFlagValue(column.get(canonicalName), null);
            if (normalized != null)
            {
                column.put(canonicalName, normalized);
                return;
            }
        }
        for (String alias : aliases)
        {
            if (column.containsKey(alias))
            {
                column.put(canonicalName, normalizeFlagValue(column.get(alias), null));
                return;
            }
        }
    }

    private String normalizeFlagValue(Object value, String defaultValue)
    {
        if (value == null)
        {
            return defaultValue;
        }
        if (value instanceof Boolean)
        {
            return (Boolean) value ? "1" : "0";
        }
        if (value instanceof Number)
        {
            return ((Number) value).intValue() == 0 ? "0" : "1";
        }
        String text = StringUtils.defaultString(String.valueOf(value)).trim().toLowerCase(Locale.ENGLISH);
        if ("1".equals(text) || "true".equals(text) || "yes".equals(text) || "y".equals(text))
        {
            return "1";
        }
        if ("0".equals(text) || "false".equals(text) || "no".equals(text) || "n".equals(text))
        {
            return "0";
        }
        return defaultValue;
    }

    private String stripMarkdownFence(String content)
    {
        String text = StringUtils.defaultString(content).trim();
        if (text.startsWith("```"))
        {
            text = text.replaceFirst("^```[a-zA-Z]*\\s*", "");
            text = text.replaceFirst("\\s*```$", "");
        }
        return text;
    }

    private void normalizeAndBuildSql(DatabaseDesignResponse response)
    {
        List<String> ddlList = new ArrayList<String>();
        if (response.getTables() == null)
        {
            return;
        }
        normalizeDatabaseNames(response);
        int tableSort = 1;
        for (DatabaseTableDesign table : response.getTables())
        {
            table.setClassName(StringUtils.defaultIfEmpty(table.getClassName(), toClassName(table.getTableName())));
            table.setBusinessName(StringUtils.defaultIfEmpty(table.getBusinessName(), table.getTableName()));
            table.setFunctionName(StringUtils.defaultIfEmpty(table.getFunctionName(), table.getTableComment()));
            table.setTplCategory(StringUtils.defaultIfEmpty(table.getTplCategory(), "crud"));
            table.setTplWebType(StringUtils.defaultIfEmpty(table.getTplWebType(), "element-plus"));
            table.setSort(table.getSort() == null ? tableSort : table.getSort());
            int columnSort = 1;
            if (table.getColumns() != null)
            {
                for (DatabaseColumnDesign column : table.getColumns())
                {
                    column.setColumnType(normalizeColumnType(column.getColumnType()));
                    column.setJavaType(StringUtils.defaultIfEmpty(column.getJavaType(), guessJavaType(column.getColumnType())));
                    column.setJavaField(StringUtils.defaultIfEmpty(column.getJavaField(), toJavaField(column.getColumnName())));
                    column.setIsPk(normalizeFlagValue(column.getIsPk(), "0"));
                    column.setIsIncrement(normalizeFlagValue(column.getIsIncrement(), "0"));
                    column.setIsRequired(normalizeFlagValue(column.getIsRequired(), "0"));
                    column.setIsInsert(StringUtils.defaultIfEmpty(column.getIsInsert(), "1"));
                    column.setIsEdit(StringUtils.defaultIfEmpty(column.getIsEdit(), "1"));
                    column.setIsList(StringUtils.defaultIfEmpty(column.getIsList(), "1"));
                    column.setIsQuery(StringUtils.defaultIfEmpty(column.getIsQuery(), "0"));
                    column.setQueryType(StringUtils.defaultIfEmpty(column.getQueryType(), "EQ"));
                    column.setHtmlType(StringUtils.defaultIfEmpty(column.getHtmlType(), "input"));
                    column.setSort(column.getSort() == null ? columnSort : column.getSort());
                    columnSort++;
                }
            }
            normalizePrimaryKey(table);
            refreshColumnSort(table);
            table.setCreateTableSql(ddlBuilder.build(table));
            if (!validator.isSafeSql(table.getCreateTableSql()))
            {
                throw new ServiceException("生成SQL包含危险语句");
            }
            ddlList.add(table.getCreateTableSql());
            tableSort++;
        }
        response.setSql(StringUtils.join(ddlList, "\n\n"));
    }

    private void normalizeBusinessActions(DatabaseDesignResponse response)
    {
        if (response.getBusinessActions() == null)
        {
            response.setBusinessActions(new ArrayList<BusinessActionDesign>());
            return;
        }
        Set<String> actionCodes = new HashSet<String>();
        Set<String> columnNames = collectColumnNames(response);
        Map<String, Set<String>> columnNamesByTable = collectColumnNamesByTable(response);
        List<BusinessActionDesign> normalizedActions = new ArrayList<BusinessActionDesign>();
        int actionIndex = 1;
        for (BusinessActionDesign action : response.getBusinessActions())
        {
            if (action == null)
            {
                continue;
            }
            String code = uniqueBusinessCode(action.getCode(), actionIndex, actionCodes);
            action.setCode(code);
            action.setName(StringUtils.defaultIfEmpty(trimToLength(action.getName(), 80), code));
            action.setOwnerTable(normalizeDatabaseName(action.getOwnerTable(), "table_" + actionIndex));
            action.setMethod(normalizeHttpMethod(action.getMethod()));
            action.setPath(normalizeBusinessPath(action.getPath(), code));
            action.setTransaction(action.getTransaction() == null ? Boolean.TRUE : action.getTransaction());
            action.setRequestFields(normalizeExistingFieldList(action.getRequestFields(), columnNames));
            action.setRules(trimStringList(action.getRules(), 200));
            normalizeBusinessRuleChecks(action, columnNames, columnNamesByTable);
            normalizeBusinessEffects(action, columnNames, columnNamesByTable);
            if (!isCrudBusinessAction(action))
            {
                normalizedActions.add(action);
            }
            actionIndex++;
        }
        response.setBusinessActions(normalizedActions);
    }

    private boolean isCrudBusinessAction(BusinessActionDesign action)
    {
        String code = StringUtils.defaultString(action.getCode()).toLowerCase(Locale.ENGLISH);
        String name = normalizeDatabaseName(action.getName(), "action");
        String path = StringUtils.defaultString(action.getPath()).toLowerCase(Locale.ENGLISH);
        return startsWithCrudToken(code) || startsWithCrudToken(name) || containsCrudPathToken(path);
    }

    private boolean startsWithCrudToken(String value)
    {
        String[] tokens = new String[] {"create", "add", "new", "read", "query", "list", "page", "get", "view",
                "detail", "edit", "update", "delete", "remove", "import", "export"};
        String normalized = StringUtils.defaultString(value).toLowerCase(Locale.ENGLISH);
        for (String token : tokens)
        {
            if (normalized.equals(token) || normalized.startsWith(token + "_") || normalized.startsWith(token + "-"))
            {
                return true;
            }
        }
        return false;
    }

    private boolean containsCrudPathToken(String path)
    {
        String[] tokens = new String[] {"create", "add", "list", "page", "detail", "edit", "update", "delete",
                "remove", "import", "export"};
        String normalized = StringUtils.defaultString(path).toLowerCase(Locale.ENGLISH);
        for (String token : tokens)
        {
            if (normalized.endsWith("/" + token) || normalized.contains("/" + token + "/"))
            {
                return true;
            }
        }
        return false;
    }

    private String uniqueBusinessCode(String rawCode, int index, Set<String> usedCodes)
    {
        String baseCode = normalizeDatabaseName(rawCode, "action_" + index);
        String uniqueCode = baseCode;
        int suffix = 2;
        while (usedCodes.contains(uniqueCode))
        {
            uniqueCode = appendNameSuffix(baseCode, "_" + suffix);
            suffix++;
        }
        usedCodes.add(uniqueCode);
        return uniqueCode;
    }

    private String normalizeHttpMethod(String method)
    {
        String normalized = StringUtils.defaultString(method).trim().toUpperCase(Locale.ENGLISH);
        if ("GET".equals(normalized) || "POST".equals(normalized) || "PUT".equals(normalized) || "DELETE".equals(normalized))
        {
            return normalized;
        }
        return "POST";
    }

    private String normalizeBusinessPath(String path, String code)
    {
        String normalized = StringUtils.defaultString(path).trim();
        normalized = normalized.replaceAll("[^A-Za-z0-9_./-]+", "-").replaceAll("-+", "-");
        if (!normalized.startsWith("/"))
        {
            normalized = "/" + normalized;
        }
        normalized = normalized.replaceAll("/{2,}", "/");
        if (normalized.length() < 2)
        {
            normalized = "/business/" + code;
        }
        if (normalized.length() > 180)
        {
            normalized = normalized.substring(0, 180).replaceAll("[./_-]+$", "");
        }
        return normalized;
    }

    private List<String> normalizeExistingFieldList(List<String> fields, Set<String> columnNames)
    {
        List<String> normalizedFields = new ArrayList<String>();
        if (fields == null)
        {
            return normalizedFields;
        }
        for (int i = 0; i < fields.size(); i++)
        {
            String fieldName = normalizeDatabaseName(fields.get(i), "field_" + (i + 1));
            if (columnNames.contains(fieldName))
            {
                normalizedFields.add(fieldName);
            }
        }
        return normalizedFields;
    }

    private Set<String> collectColumnNames(DatabaseDesignResponse response)
    {
        Set<String> columnNames = new HashSet<String>();
        if (response == null || response.getTables() == null)
        {
            return columnNames;
        }
        for (DatabaseTableDesign table : response.getTables())
        {
            if (table == null || table.getColumns() == null)
            {
                continue;
            }
            for (DatabaseColumnDesign column : table.getColumns())
            {
                if (column != null && StringUtils.isNotBlank(column.getColumnName()))
                {
                    columnNames.add(column.getColumnName());
                }
            }
        }
        return columnNames;
    }

    private Map<String, Set<String>> collectColumnNamesByTable(DatabaseDesignResponse response)
    {
        Map<String, Set<String>> columnNamesByTable = new HashMap<String, Set<String>>();
        if (response == null || response.getTables() == null)
        {
            return columnNamesByTable;
        }
        for (DatabaseTableDesign table : response.getTables())
        {
            if (table == null || StringUtils.isBlank(table.getTableName()) || table.getColumns() == null)
            {
                continue;
            }
            Set<String> tableColumnNames = new HashSet<String>();
            for (DatabaseColumnDesign column : table.getColumns())
            {
                if (column != null && StringUtils.isNotBlank(column.getColumnName()))
                {
                    tableColumnNames.add(column.getColumnName());
                }
            }
            columnNamesByTable.put(table.getTableName(), tableColumnNames);
        }
        return columnNamesByTable;
    }

    private List<String> trimStringList(List<String> values, int maxLength)
    {
        List<String> normalizedValues = new ArrayList<String>();
        if (values == null)
        {
            return normalizedValues;
        }
        for (String value : values)
        {
            String normalized = trimToLength(value, maxLength);
            if (StringUtils.isNotBlank(normalized))
            {
                normalizedValues.add(normalized);
            }
        }
        return normalizedValues;
    }

    private void normalizeBusinessRuleChecks(BusinessActionDesign action, Set<String> columnNames, Map<String, Set<String>> columnNamesByTable)
    {
        if (action.getRuleChecks() == null)
        {
            action.setRuleChecks(new ArrayList<BusinessActionRuleDesign>());
            return;
        }
        List<BusinessActionRuleDesign> normalizedRules = new ArrayList<BusinessActionRuleDesign>();
        int ruleIndex = 1;
        for (BusinessActionRuleDesign rule : action.getRuleChecks())
        {
            if (rule == null)
            {
                continue;
            }
            rule.setType(normalizeRuleType(rule.getType(), ruleIndex));
            rule.setTargetTable(StringUtils.isBlank(rule.getTargetTable()) ? "" : normalizeDatabaseName(rule.getTargetTable(), "table_" + ruleIndex));
            String rawTargetField = StringUtils.isBlank(rule.getTargetField()) ? "" : normalizeDatabaseName(rule.getTargetField(), "field_" + ruleIndex);
            rule.setTargetField(resolveStrictTargetField(rawTargetField, rule.getTargetTable(), columnNamesByTable, columnNames));
            rule.setExpectedValue(trimToLength(rule.getExpectedValue(), 200));
            rule.setExpectedValues(trimStringList(rule.getExpectedValues(), 200));
            rule.setCompareValue(trimToLength(rule.getCompareValue(), 80));
            rule.setConditionFields(normalizeExistingFieldList(rule.getConditionFields(), columnNames));
            rule.setMessage(trimToLength(rule.getMessage(), 200));
            normalizedRules.add(rule);
            ruleIndex++;
        }
        action.setRuleChecks(normalizedRules);
    }

    private String resolveStrictTargetField(String targetField, String targetTable, Map<String, Set<String>> columnNamesByTable, Set<String> columnNames)
    {
        if (StringUtils.isBlank(targetField))
        {
            return "";
        }
        Set<String> tableColumnNames = columnNamesByTable == null ? null : columnNamesByTable.get(targetTable);
        if (tableColumnNames != null)
        {
            return tableColumnNames.contains(targetField) ? targetField : "";
        }
        return columnNames != null && columnNames.contains(targetField) ? targetField : "";
    }

    private String normalizeRuleType(String type, int index)
    {
        String normalized = StringUtils.defaultString(type).trim().toUpperCase(Locale.ENGLISH);
        normalized = normalized.replaceAll("[^A-Z0-9]+", "_").replaceAll("_+", "_").replaceAll("^_+|_+$", "");
        return StringUtils.defaultIfEmpty(normalized, "RULE_" + index);
    }

    private void normalizeBusinessEffects(BusinessActionDesign action, Set<String> columnNames, Map<String, Set<String>> columnNamesByTable)
    {
        if (action.getEffects() == null)
        {
            action.setEffects(new ArrayList<BusinessActionEffectDesign>());
            return;
        }
        List<BusinessActionEffectDesign> normalizedEffects = new ArrayList<BusinessActionEffectDesign>();
        int effectIndex = 1;
        for (BusinessActionEffectDesign effect : action.getEffects())
        {
            if (effect == null)
            {
                continue;
            }
            effect.setType(normalizeEffectType(effect.getType(), effectIndex));
            effect.setTargetTable(StringUtils.isBlank(effect.getTargetTable()) ? "" : normalizeDatabaseName(effect.getTargetTable(), "table_" + effectIndex));
            String rawTargetField = StringUtils.isBlank(effect.getTargetField()) ? "" : normalizeDatabaseName(effect.getTargetField(), "field_" + effectIndex);
            String targetField = resolveEffectTargetField(rawTargetField, effect.getTargetTable(), columnNamesByTable, columnNames);
            if ("UPDATE_FIELD".equals(effect.getType()) && (StringUtils.isBlank(effect.getTargetTable()) || StringUtils.isBlank(targetField)))
            {
                effectIndex++;
                continue;
            }
            effect.setTargetField(targetField);
            effect.setValue(trimToLength(effect.getValue(), 200));
            effect.setValues(normalizeEffectValues(effect.getValues(), effect.getTargetTable(), columnNamesByTable, columnNames));
            effect.setAmount(trimToLength(effect.getAmount(), 80));
            effect.setMinValue(trimToLength(effect.getMinValue(), 80));
            if (StringUtils.isNotBlank(rawTargetField) && StringUtils.isNotBlank(targetField) && !rawTargetField.equals(targetField))
            {
                effect.setValue(rewriteEffectValue(effect.getValue(), rawTargetField, targetField));
            }
            effect.setConditionFields(normalizeExistingFieldList(effect.getConditionFields(), columnNames));
            effect.setMessage(trimToLength(effect.getMessage(), 200));
            effect.setDescription(trimToLength(effect.getDescription(), 200));
            normalizedEffects.add(effect);
            effectIndex++;
        }
        action.setEffects(normalizedEffects);
    }

    private Map<String, String> normalizeEffectValues(Map<String, String> values, String targetTable,
            Map<String, Set<String>> columnNamesByTable, Set<String> columnNames)
    {
        Map<String, String> normalizedValues = new LinkedHashMap<String, String>();
        if (values == null)
        {
            return normalizedValues;
        }
        Set<String> tableColumnNames = columnNamesByTable == null ? null : columnNamesByTable.get(targetTable);
        int valueIndex = 1;
        for (Map.Entry<String, String> entry : values.entrySet())
        {
            String fieldName = normalizeDatabaseName(entry.getKey(), "field_" + valueIndex);
            boolean exists = tableColumnNames != null ? tableColumnNames.contains(fieldName)
                    : columnNames != null && columnNames.contains(fieldName);
            if (exists)
            {
                normalizedValues.put(fieldName, trimToLength(entry.getValue(), 200));
            }
            valueIndex++;
        }
        return normalizedValues;
    }

    private String resolveEffectTargetField(String targetField, String targetTable, Map<String, Set<String>> columnNamesByTable, Set<String> columnNames)
    {
        if (StringUtils.isBlank(targetField))
        {
            return "";
        }
        Set<String> tableColumnNames = columnNamesByTable == null ? null : columnNamesByTable.get(targetTable);
        if (tableColumnNames != null)
        {
            if (tableColumnNames.contains(targetField))
            {
                return targetField;
            }
            String relatedColumn = findRelatedColumn(targetField, tableColumnNames);
            if (StringUtils.isNotBlank(relatedColumn))
            {
                return relatedColumn;
            }
        }
        return columnNames != null && columnNames.contains(targetField) ? targetField : "";
    }

    private String findRelatedColumn(String targetField, Set<String> tableColumnNames)
    {
        if (StringUtils.isBlank(targetField) || tableColumnNames == null)
        {
            return "";
        }
        String[] tokens = targetField.split("_");
        for (int i = tokens.length - 1; i >= 0; i--)
        {
            String token = tokens[i];
            if (token.length() >= 3 && tableColumnNames.contains(token))
            {
                return token;
            }
        }
        for (String columnName : tableColumnNames)
        {
            if (columnName.length() >= 3 && (targetField.endsWith("_" + columnName)
                    || targetField.startsWith(columnName + "_") || targetField.contains("_" + columnName + "_")))
            {
                return columnName;
            }
        }
        return "";
    }

    private String rewriteEffectValue(String value, String rawTargetField, String targetField)
    {
        String normalizedValue = trimToLength(value, 200);
        if (StringUtils.isBlank(normalizedValue))
        {
            return normalizedValue;
        }
        return normalizedValue.replaceAll("(?<![A-Za-z0-9_])" + Pattern.quote(rawTargetField) + "(?![A-Za-z0-9_])", targetField);
    }

    private String normalizeEffectType(String type, int index)
    {
        String normalized = StringUtils.defaultString(type).trim().toUpperCase(Locale.ENGLISH);
        normalized = normalized.replaceAll("[^A-Z0-9]+", "_").replaceAll("_+", "_").replaceAll("^_+|_+$", "");
        return StringUtils.defaultIfEmpty(normalized, "EFFECT_" + index);
    }

    private String trimToLength(String value, int maxLength)
    {
        String normalized = StringUtils.defaultString(value).trim();
        return normalized.length() <= maxLength ? normalized : normalized.substring(0, maxLength);
    }

    private void normalizeDatabaseNames(DatabaseDesignResponse response)
    {
        Set<String> tableNames = new HashSet<String>();
        int tableIndex = 1;
        for (DatabaseTableDesign table : response.getTables())
        {
            if (table == null)
            {
                continue;
            }
            table.setTableName(uniqueDatabaseName(table.getTableName(), "table", tableIndex, tableNames));
            normalizeColumnNames(table);
            tableIndex++;
        }
    }

    private void normalizeColumnNames(DatabaseTableDesign table)
    {
        if (table.getColumns() == null)
        {
            return;
        }
        Set<String> columnNames = new HashSet<String>();
        int columnIndex = 1;
        for (DatabaseColumnDesign column : table.getColumns())
        {
            if (column != null)
            {
                column.setColumnName(uniqueDatabaseName(column.getColumnName(), "field", columnIndex, columnNames));
            }
            columnIndex++;
        }
    }

    private String uniqueDatabaseName(String rawName, String fallbackPrefix, int index, Set<String> usedNames)
    {
        String baseName = normalizeDatabaseName(rawName, fallbackPrefix + "_" + index);
        String uniqueName = baseName;
        int suffix = 2;
        while (usedNames.contains(uniqueName))
        {
            uniqueName = appendNameSuffix(baseName, "_" + suffix);
            suffix++;
        }
        usedNames.add(uniqueName);
        return uniqueName;
    }

    private String normalizeDatabaseName(String rawName, String fallbackName)
    {
        String name = StringUtils.defaultString(rawName).trim();
        name = name.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2");
        name = name.replaceAll("([a-z0-9])([A-Z])", "$1_$2");
        name = name.toLowerCase(Locale.ENGLISH);
        name = name.replaceAll("[^a-z0-9]+", "_");
        name = name.replaceAll("_+", "_").replaceAll("^_+|_+$", "");
        if (StringUtils.isBlank(name))
        {
            name = fallbackName;
        }
        if (!name.substring(0, 1).matches("[a-z]"))
        {
            name = fallbackName + "_" + name;
        }
        if (name.length() < 2)
        {
            name = name + "_1";
        }
        return trimDatabaseName(name);
    }

    private String appendNameSuffix(String baseName, String suffix)
    {
        int maxBaseLength = Math.max(2, 64 - suffix.length());
        String trimmedBase = baseName.length() > maxBaseLength ? baseName.substring(0, maxBaseLength) : baseName;
        trimmedBase = trimmedBase.replaceAll("_+$", "");
        if (trimmedBase.length() < 2)
        {
            trimmedBase = "f_" + trimmedBase;
        }
        return trimDatabaseName(trimmedBase + suffix);
    }

    private String trimDatabaseName(String name)
    {
        if (name.length() <= 64)
        {
            return name;
        }
        String trimmed = name.substring(0, 64).replaceAll("_+$", "");
        return trimmed.length() < 2 ? "f_1" : trimmed;
    }

    private void normalizePrimaryKey(DatabaseTableDesign table)
    {
        if (table.getColumns() == null || table.getColumns().isEmpty())
        {
            return;
        }
        int primaryKeyIndex = -1;
        for (int i = 0; i < table.getColumns().size(); i++)
        {
            DatabaseColumnDesign column = table.getColumns().get(i);
            if ("1".equals(column.getIsPk()))
            {
                if (primaryKeyIndex < 0)
                {
                    primaryKeyIndex = i;
                    column.setIsRequired("1");
                }
                else
                {
                    column.setIsPk("0");
                    column.setIsIncrement("0");
                }
            }
        }
        if (primaryKeyIndex >= 0)
        {
            return;
        }
        DatabaseColumnDesign idColumn = findColumn(table, "id");
        if (idColumn == null)
        {
            idColumn = createIdPrimaryKeyColumn();
            table.getColumns().add(0, idColumn);
        }
        idColumn.setIsPk("1");
        idColumn.setIsIncrement("1");
        idColumn.setIsRequired("1");
        idColumn.setJavaType("Long");
        idColumn.setJavaField("id");
    }

    private DatabaseColumnDesign findColumn(DatabaseTableDesign table, String columnName)
    {
        for (DatabaseColumnDesign column : table.getColumns())
        {
            if (columnName.equals(column.getColumnName()))
            {
                return column;
            }
        }
        return null;
    }

    private DatabaseColumnDesign createIdPrimaryKeyColumn()
    {
        DatabaseColumnDesign column = new DatabaseColumnDesign();
        column.setColumnName("id");
        column.setColumnType("bigint(20)");
        column.setColumnComment("ID");
        column.setJavaType("Long");
        column.setJavaField("id");
        column.setIsPk("1");
        column.setIsIncrement("1");
        column.setIsRequired("1");
        column.setIsInsert("0");
        column.setIsEdit("0");
        column.setIsList("0");
        column.setIsQuery("0");
        column.setQueryType("EQ");
        column.setHtmlType("input");
        return column;
    }

    private String normalizeColumnType(String columnType)
    {
        String type = StringUtils.defaultString(columnType).trim().toLowerCase(Locale.ENGLISH);
        type = type.replace("`", "").replaceAll("\\s+", " ");
        type = type.replaceAll("\\s+unsigned\\b", "");
        type = type.replaceAll("\\s+(not\\s+null|null|default\\b.*|comment\\b.*|auto_increment\\b.*|primary\\s+key\\b.*)$", "");
        type = type.trim();
        if (StringUtils.isBlank(type))
        {
            return "varchar(255)";
        }
        if (type.startsWith("varchar") || "string".equals(type) || "str".equals(type))
        {
            return "varchar(" + boundedTypeNumber(type, 255, 1, 1000) + ")";
        }
        if (type.startsWith("char"))
        {
            return "char(" + boundedTypeNumber(type, 32, 1, 99) + ")";
        }
        if (type.startsWith("bigint") || "long".equals(type) || "long integer".equals(type) || "int64".equals(type))
        {
            return "bigint(20)";
        }
        if (type.startsWith("int") || "integer".equals(type) || type.startsWith("tinyint")
                || type.startsWith("smallint") || type.startsWith("mediumint") || "bool".equals(type)
                || "boolean".equals(type))
        {
            return "int(11)";
        }
        if (type.startsWith("decimal") || "numeric".equals(type) || "number".equals(type)
                || "float".equals(type) || "double".equals(type))
        {
            return normalizeDecimalType(type);
        }
        if ("datetime".equals(type) || "timestamp".equals(type) || "localdatetime".equals(type))
        {
            return "datetime";
        }
        if ("date".equals(type) || "localdate".equals(type))
        {
            return "date";
        }
        if ("longtext".equals(type))
        {
            return "longtext";
        }
        if ("text".equals(type) || "mediumtext".equals(type) || "json".equals(type) || "object".equals(type))
        {
            return "text";
        }
        return "varchar(255)";
    }

    private int boundedTypeNumber(String type, int defaultValue, int min, int max)
    {
        int start = type.indexOf('(');
        int end = type.indexOf(')', start + 1);
        if (start < 0 || end <= start)
        {
            return defaultValue;
        }
        try
        {
            int value = Integer.parseInt(type.substring(start + 1, end).split(",")[0].trim());
            return Math.max(min, Math.min(max, value));
        }
        catch (NumberFormatException e)
        {
            return defaultValue;
        }
    }

    private String normalizeDecimalType(String type)
    {
        int precision = 10;
        int scale = 2;
        int start = type.indexOf('(');
        int end = type.indexOf(')', start + 1);
        if (start >= 0 && end > start)
        {
            String[] parts = type.substring(start + 1, end).split(",");
            try
            {
                precision = Integer.parseInt(parts[0].trim());
                if (parts.length > 1)
                {
                    scale = Integer.parseInt(parts[1].trim());
                }
            }
            catch (NumberFormatException e)
            {
                precision = 10;
                scale = 2;
            }
        }
        precision = Math.max(1, Math.min(65, precision));
        scale = Math.max(0, Math.min(30, scale));
        scale = Math.min(scale, precision);
        return "decimal(" + precision + "," + scale + ")";
    }

    private void refreshColumnSort(DatabaseTableDesign table)
    {
        if (table.getColumns() == null)
        {
            return;
        }
        int sort = 1;
        for (DatabaseColumnDesign column : table.getColumns())
        {
            column.setSort(sort++);
        }
    }

    private DatabaseDesignResponse loadSavedDatabaseDesign(Long projectId)
    {
        List<FrontProjectTable> savedTables = frontProjectTableMapper.selectTablesByProjectId(projectId);
        if (savedTables == null || savedTables.isEmpty())
        {
            throw new ServiceException("Please save database design before generating business blueprint");
        }
        DatabaseDesignResponse response = new DatabaseDesignResponse();
        response.setProjectId(projectId);
        List<DatabaseTableDesign> tables = new ArrayList<DatabaseTableDesign>();
        for (FrontProjectTable savedTable : savedTables)
        {
            if (savedTable == null)
            {
                continue;
            }
            DatabaseTableDesign table = toDatabaseTableDesign(savedTable);
            List<FrontProjectColumn> savedColumns = frontProjectColumnMapper.selectColumnsByTableId(savedTable.getTableId());
            List<DatabaseColumnDesign> columns = new ArrayList<DatabaseColumnDesign>();
            if (savedColumns != null)
            {
                for (FrontProjectColumn savedColumn : savedColumns)
                {
                    if (savedColumn != null)
                    {
                        columns.add(toDatabaseColumnDesign(savedColumn));
                    }
                }
            }
            table.setColumns(columns);
            tables.add(table);
        }
        response.setTables(tables);
        response.setBusinessActions(new ArrayList<BusinessActionDesign>());
        return response;
    }

    private void persistDatabase(Long userId, Long projectId, DatabaseDesignResponse response)
    {
        frontProjectColumnMapper.deleteColumnsByProjectId(projectId);
        frontProjectTableMapper.deleteTablesByProjectId(projectId);
        for (DatabaseTableDesign tableDesign : response.getTables())
        {
            FrontProjectTable table = toFrontProjectTable(projectId, userId, tableDesign);
            frontProjectTableMapper.insertFrontProjectTable(table);
            for (DatabaseColumnDesign columnDesign : tableDesign.getColumns())
            {
                FrontProjectColumn column = toFrontProjectColumn(projectId, table.getTableId(), columnDesign);
                frontProjectColumnMapper.insertFrontProjectColumn(column);
            }
        }
    }

    private void markDatabaseGenerated(Long projectId, GenerateDatabaseRequest request)
    {
        FrontProject project = new FrontProject();
        project.setProjectId(projectId);
        project.setGenerateStatus("1");
        project.setPreviewStatus("0");
        project.setRequirementKeyword(request == null ? null : request.getExtraRequirements());
        project.setProjectDesc(request == null ? null : request.getProjectDesc());
        project.setIndustryTemplate(request == null ? null : request.getIndustryTemplate());
        project.setBusinessBlueprint("[]");
        frontProjectMapper.updateFrontProject(project);
    }

    private void markBusinessBlueprintGenerated(Long projectId, List<BusinessActionDesign> actions, GenerateBusinessBlueprintRequest request)
    {
        FrontProject project = new FrontProject();
        project.setProjectId(projectId);
        project.setPreviewStatus("0");
        project.setRequirementKeyword(request == null ? null : request.getExtraRequirements());
        project.setProjectDesc(request == null ? null : request.getProjectDesc());
        project.setIndustryTemplate(request == null ? null : request.getIndustryTemplate());
        project.setBusinessBlueprint(JSON.toJSONString(actions == null ? new ArrayList<BusinessActionDesign>() : actions));
        frontProjectMapper.updateFrontProject(project);
    }

    private void markAppBlueprintGenerated(Long projectId, AppBlueprintDesign blueprint, GenerateAppBlueprintRequest request)
    {
        FrontProject project = new FrontProject();
        project.setProjectId(projectId);
        project.setGenerateStatus("0");
        project.setPreviewStatus("0");
        project.setAppBlueprint(JSON.toJSONString(blueprint));
        project.setRequirementKeyword(request == null ? null : request.getExtraRequirements());
        project.setProjectDesc(request == null ? null : request.getProjectDesc());
        project.setIndustryTemplate(request == null ? null : request.getIndustryTemplate());
        frontProjectMapper.updateFrontProject(project);
    }

    private FrontProjectGeneration newGeneration(Long projectId, Long userId, String prompt, String generateType)
    {
        FrontProjectGeneration generation = new FrontProjectGeneration();
        generation.setProjectId(projectId);
        generation.setUserId(userId);
        generation.setGenerateType(generateType);
        generation.setProvider("deepseek");
        generation.setPromptSummary(StringUtils.abbreviate(prompt, 255));
        generation.setRequestPayload(prompt);
        return generation;
    }

    private String sanitizeErrorMessage(String message)
    {
        if (message == null)
        {
            return null;
        }
        String sanitized = message.replaceAll("[\\p{Cntrl}]", " ");
        if (sanitized.length() <= MAX_ERROR_MESSAGE_LENGTH)
        {
            return sanitized;
        }
        return sanitized.substring(0, MAX_ERROR_MESSAGE_LENGTH);
    }

    private DatabaseTableDesign toDatabaseTableDesign(FrontProjectTable source)
    {
        DatabaseTableDesign table = new DatabaseTableDesign();
        table.setTableId(source.getTableId());
        table.setTableName(source.getTableName());
        table.setTableComment(source.getTableComment());
        table.setClassName(source.getClassName());
        table.setModuleName(source.getModuleName());
        table.setBusinessName(source.getBusinessName());
        table.setFunctionName(source.getFunctionName());
        table.setTplCategory(source.getTplCategory());
        table.setTplWebType(source.getTplWebType());
        table.setOptions(source.getOptions());
        table.setCreateTableSql(source.getCreateTableSql());
        table.setSort(source.getSort());
        return table;
    }

    private DatabaseColumnDesign toDatabaseColumnDesign(FrontProjectColumn source)
    {
        DatabaseColumnDesign column = new DatabaseColumnDesign();
        column.setColumnId(source.getColumnId());
        column.setTableId(source.getTableId());
        column.setColumnName(source.getColumnName());
        column.setColumnComment(source.getColumnComment());
        column.setColumnType(source.getColumnType());
        column.setJavaType(source.getJavaType());
        column.setJavaField(source.getJavaField());
        column.setIsPk(source.getIsPk());
        column.setIsIncrement(source.getIsIncrement());
        column.setIsRequired(source.getIsRequired());
        column.setIsInsert(source.getIsInsert());
        column.setIsEdit(source.getIsEdit());
        column.setIsList(source.getIsList());
        column.setIsQuery(source.getIsQuery());
        column.setQueryType(source.getQueryType());
        column.setHtmlType(source.getHtmlType());
        column.setDictType(source.getDictType());
        column.setDefaultValue(source.getDefaultValue());
        column.setSort(source.getSort());
        return column;
    }

    private FrontProjectTable toFrontProjectTable(Long projectId, Long userId, DatabaseTableDesign source)
    {
        FrontProjectTable table = new FrontProjectTable();
        table.setProjectId(projectId);
        table.setUserId(userId);
        table.setTableName(source.getTableName());
        table.setTableComment(source.getTableComment());
        table.setClassName(source.getClassName());
        table.setModuleName(source.getModuleName());
        table.setBusinessName(source.getBusinessName());
        table.setFunctionName(source.getFunctionName());
        table.setTplCategory(source.getTplCategory());
        table.setTplWebType(source.getTplWebType());
        table.setOptions(source.getOptions());
        table.setCreateTableSql(source.getCreateTableSql());
        table.setSort(source.getSort());
        table.setStatus("0");
        return table;
    }

    private FrontProjectColumn toFrontProjectColumn(Long projectId, Long tableId, DatabaseColumnDesign source)
    {
        FrontProjectColumn column = new FrontProjectColumn();
        column.setProjectId(projectId);
        column.setTableId(tableId);
        column.setColumnName(source.getColumnName());
        column.setColumnComment(source.getColumnComment());
        column.setColumnType(source.getColumnType());
        column.setJavaType(source.getJavaType());
        column.setJavaField(source.getJavaField());
        column.setIsPk(source.getIsPk());
        column.setIsIncrement(source.getIsIncrement());
        column.setIsRequired(source.getIsRequired());
        column.setIsInsert(source.getIsInsert());
        column.setIsEdit(source.getIsEdit());
        column.setIsList(source.getIsList());
        column.setIsQuery(source.getIsQuery());
        column.setQueryType(source.getQueryType());
        column.setHtmlType(source.getHtmlType());
        column.setDictType(source.getDictType());
        column.setDefaultValue(source.getDefaultValue());
        column.setSort(source.getSort());
        return column;
    }

    private String toClassName(String tableName)
    {
        StringBuilder builder = new StringBuilder();
        for (String part : StringUtils.defaultString(tableName).split("_"))
        {
            if (part.length() > 0)
            {
                builder.append(part.substring(0, 1).toUpperCase(Locale.ENGLISH)).append(part.substring(1));
            }
        }
        return builder.toString();
    }

    private String toJavaField(String columnName)
    {
        String className = toClassName(columnName);
        if (className.length() == 0)
        {
            return className;
        }
        return className.substring(0, 1).toLowerCase(Locale.ENGLISH) + className.substring(1);
    }

    private String guessJavaType(String columnType)
    {
        String type = StringUtils.defaultString(columnType).toLowerCase(Locale.ENGLISH);
        if (type.startsWith("bigint"))
        {
            return "Long";
        }
        if (type.startsWith("int"))
        {
            return "Integer";
        }
        if (type.startsWith("decimal"))
        {
            return "BigDecimal";
        }
        if ("datetime".equals(type))
        {
            return "Date";
        }
        return "String";
    }

    private int maxTables()
    {
        return deepSeekProperties.getMaxTables() == null ? 12 : deepSeekProperties.getMaxTables();
    }

    private int maxColumnsPerTable()
    {
        return deepSeekProperties.getMaxColumnsPerTable() == null ? 30 : deepSeekProperties.getMaxColumnsPerTable();
    }
}
