package com.ruoyi.generator.util;

import java.util.Collections;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import com.ruoyi.generator.domain.*;
import com.ruoyi.generator.domain.front.FrontProjectPageDesign;
import com.ruoyi.generator.domain.front.dto.AppMenuDesign;
import com.ruoyi.generator.domain.front.dto.AppPageDesign;
import org.apache.velocity.VelocityContext;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.constant.GenConstants;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;

/**
 * 模板处理工具类
 *
 * @author ruoyi
 */
public class VelocityUtils {
    /**
     * 项目空间路径
     */
    private static final String PROJECT_PATH = "main/java";

    /**
     * mybatis空间路径
     */
    private static final String MYBATIS_PATH = "main/resources/mapper";

    /**
     * 默认上级菜单，系统工具
     */
    private static final String DEFAULT_PARENT_MENU_ID = "3";

    /**
     * 设置模板变量信息
     *
     * @return 模板列表
     */
    public static VelocityContext prepareContext(GenTable genTable) {
        TableOperationResolver.applyDefaults(genTable);
        String moduleName = genTable.getModuleName();
        String businessName = genTable.getBusinessName();
        String packageName = genTable.getPackageName();
        String tplCategory = genTable.getTplCategory();
        String functionName = genTable.getFunctionName();

        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("tplCategory", genTable.getTplCategory());
        velocityContext.put("tableName", genTable.getTableName());
        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
        velocityContext.put("ClassName", genTable.getClassName());
        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
        velocityContext.put("moduleName", genTable.getModuleName());
        velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
        velocityContext.put("businessName", genTable.getBusinessName());
        velocityContext.put("basePackage", getPackagePrefix(packageName));
        velocityContext.put("packageName", packageName);
        velocityContext.put("author", genTable.getFunctionAuthor());
        velocityContext.put("datetime", DateUtils.getDate());
        velocityContext.put("pkColumn", genTable.getPkColumn());
        velocityContext.put("importList", getImportList(genTable));
        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
        velocityContext.put("columns", genTable.getColumns());
        velocityContext.put("table", genTable);
        velocityContext.put("adminMenuName", genTable.getAdminMenuName());
        velocityContext.put("allowAdd", genTable.getAllowAdd());
        velocityContext.put("allowEdit", genTable.getAllowEdit());
        velocityContext.put("allowRemove", genTable.getAllowRemove());
        velocityContext.put("removeType", genTable.getRemoveType());
        velocityContext.put("operationNote", genTable.getOperationNote());
        velocityContext.put("dicts", getDicts(genTable));
        List<Map<String, String>> qingRemoteSelects = getQingRemoteSelects(genTable);
        velocityContext.put("qingRemoteSelects", qingRemoteSelects);
        velocityContext.put("qingRemoteSelectFields", getQingRemoteSelectFields(qingRemoteSelects));
        GenTableColumn qingCurrentUserColumn = getQingCurrentUserColumn(genTable);
        velocityContext.put("qingCurrentUserColumn", qingCurrentUserColumn);
        velocityContext.put("hasQingCurrentUserColumn", qingCurrentUserColumn != null);
        putPageDesignDefaults(velocityContext);
        setMenuVelocityContext(velocityContext, genTable);
        if (GenConstants.TPL_TREE.equals(tplCategory)) {
            setTreeVelocityContext(velocityContext, genTable);
        }
        if (GenConstants.TPL_SUB.equals(tplCategory)) {
            setSubVelocityContext(velocityContext, genTable);
        }

        // 根据项目功能模块配置设置代码片段变量
        List<SysCodeSnippet> codeList = genTable.getCodeSnippetList();
        List<SysProjectModule> modules = genTable.getModules();
        if (!Objects.isNull(codeList)) {
            for (SysCodeSnippet code : codeList) {
                if (StringUtils.isNotEmpty(code.getInsertPoint())) {
                    velocityContext.put(code.getInsertPoint(), "");
                }
            }
            for (SysCodeSnippet code : codeList) {
                if (matchesSnippet(code, modules, genTable)) {
                    String existing = StringUtils.defaultString((String) velocityContext.get(code.getInsertPoint()));
                    String value = StringUtils.isEmpty(existing) ? code.getSnippetContent() : existing + System.lineSeparator() + code.getSnippetContent();
                    velocityContext.put(code.getInsertPoint(), value);
                }
            }
        }
        return velocityContext;
    }

    private static boolean matchesSnippet(SysCodeSnippet code, List<SysProjectModule> modules, GenTable genTable) {
        if (code == null || StringUtils.isEmpty(code.getInsertPoint()) || StringUtils.isEmpty(code.getSnippetContent())) {
            return false;
        }
        if (StringUtils.isNotEmpty(code.getStatus()) && !"0".equals(code.getStatus())) {
            return false;
        }
        if (StringUtils.isNotEmpty(code.getDelFlag()) && !"0".equals(code.getDelFlag())) {
            return false;
        }
        if (StringUtils.isNotEmpty(code.getRelationTableName()) && !code.getRelationTableName().equals(genTable.getTableName())) {
            return false;
        }
        if (code.getModuleId() == null) {
            return true;
        }
        if (StringUtils.isEmpty(modules)) {
            return false;
        }
        return modules.stream().anyMatch(module -> code.getModuleId().equals(module.getModuleId())
                && (StringUtils.isEmpty(module.getStatus()) || "0".equals(module.getStatus())));
    }

    /**
     * 设置模板变量信息
     *
     * @return 模板列表
     */
    public static VelocityContext prepareContextProject(GenProject genProject) {
        VelocityContext velocityContext = new VelocityContext();
        String packageName = StringUtils.defaultString(genProject.getPackageName());
        String projectName = StringUtils.defaultString(genProject.getProjectName());
        String projectFileName = StringUtils.defaultString(genProject.getProjectFileName());
        String projectClassName = toProjectClassName(projectFileName);

        velocityContext.put("packageName", packageName);
        velocityContext.put("projectName", projectName);
        velocityContext.put("projectFileName", projectFileName);
        velocityContext.put("projectClassName", projectClassName);
        velocityContext.put("functionName", StringUtils.isNotEmpty(projectName) ? projectName : projectFileName);
        velocityContext.put("businessName", StringUtils.isNotEmpty(projectFileName) ? projectFileName : projectName);
        velocityContext.put("BusinessName", projectClassName);
        velocityContext.put("ClassName", projectClassName);
        velocityContext.put("className", StringUtils.uncapitalize(projectClassName));
        List<GenTable> tables = genProject.getTables() == null
                ? Collections.<GenTable>emptyList() : genProject.getTables();
        velocityContext.put("tables", tables);
        velocityContext.put("hasTables", StringUtils.isNotEmpty(tables));
        velocityContext.put("appBlueprint", genProject.getAppBlueprint());
        List<?> appRoles = genProject.getAppRoles() == null
                ? Collections.emptyList() : genProject.getAppRoles();
        List<?> frontendMenus = genProject.getFrontendMenus() == null
                ? Collections.emptyList() : genProject.getFrontendMenus();
        List<?> adminMenus = genProject.getAdminMenus() == null
                ? Collections.emptyList() : genProject.getAdminMenus();
        List<FrontProjectPageDesign> pageDesigns = genProject.getPageDesigns() == null
                ? Collections.<FrontProjectPageDesign>emptyList() : genProject.getPageDesigns();
        List<FrontProjectPageDesign> frontendPageDesigns = new ArrayList<FrontProjectPageDesign>();
        List<FrontProjectPageDesign> adminPageDesigns = new ArrayList<FrontProjectPageDesign>();
        Map<String, FrontProjectPageDesign> pageDesignsByCode = new LinkedHashMap<String, FrontProjectPageDesign>();
        Map<String, FrontProjectPageDesign> pageDesignsByTable = new LinkedHashMap<String, FrontProjectPageDesign>();
        Map<String, FrontProjectPageDesign> adminPageDesignsByCode = new LinkedHashMap<String, FrontProjectPageDesign>();
        Map<String, FrontProjectPageDesign> adminPageDesignsByTable = new LinkedHashMap<String, FrontProjectPageDesign>();
        for (FrontProjectPageDesign pageDesign : pageDesigns) {
            if (pageDesign == null) {
                continue;
            }
            enrichPageDesign(pageDesign);
            boolean adminScope = "admin".equals(pageDesign.getPageScope());
            List<FrontProjectPageDesign> scopedDesigns = adminScope ? adminPageDesigns : frontendPageDesigns;
            Map<String, FrontProjectPageDesign> scopedByCode = adminScope ? adminPageDesignsByCode : pageDesignsByCode;
            Map<String, FrontProjectPageDesign> scopedByTable = adminScope ? adminPageDesignsByTable : pageDesignsByTable;
            scopedDesigns.add(pageDesign);
            if (StringUtils.isNotEmpty(pageDesign.getPageCode())) {
                scopedByCode.put(pageDesign.getPageCode(), pageDesign);
            }
            if (StringUtils.isNotEmpty(pageDesign.getTableName()) && !scopedByTable.containsKey(pageDesign.getTableName())) {
                scopedByTable.put(pageDesign.getTableName(), pageDesign);
            }
        }
        List<AppPageDesign> blueprintFrontendPages = genProject.getFrontendPages() == null
                ? Collections.<AppPageDesign>emptyList() : genProject.getFrontendPages();
        List<AppPageDesign> frontendPages = mergeFrontendPageDesigns(blueprintFrontendPages, frontendPageDesigns);
        List<Map<String, Object>> frontendNavItems = buildFrontendNavItems(
                genProject.getFrontendMenus(), frontendPages, tables, pageDesignsByCode);
        List<Map<String, Object>> frontendRouteItems = buildFrontendRouteItems(
                frontendNavItems, frontendPages, tables, pageDesignsByCode);
        List<Map<String, Object>> frontendRouteComponents = buildFrontendRouteComponents(frontendRouteItems);
        velocityContext.put("appRoles", appRoles);
        velocityContext.put("frontendMenus", frontendMenus);
        velocityContext.put("adminMenus", adminMenus);
        velocityContext.put("frontendPages", frontendPages);
        velocityContext.put("frontendNavItems", frontendNavItems);
        velocityContext.put("frontendRouteItems", frontendRouteItems);
        velocityContext.put("frontendRouteComponents", frontendRouteComponents);
        velocityContext.put("defaultFrontendPath", resolveDefaultFrontendPath(frontendNavItems, frontendRouteItems));
        velocityContext.put("hasAppBlueprint", genProject.getAppBlueprint() != null);
        velocityContext.put("hasAppRoles", StringUtils.isNotEmpty(appRoles));
        velocityContext.put("hasFrontendMenus", StringUtils.isNotEmpty(frontendMenus));
        velocityContext.put("hasFrontendNavItems", StringUtils.isNotEmpty(frontendNavItems));
        velocityContext.put("hasFrontendRouteItems", StringUtils.isNotEmpty(frontendRouteItems));
        velocityContext.put("hasAdminMenus", StringUtils.isNotEmpty(adminMenus));
        List<?> businessActions = genProject.getBusinessActions() == null
                ? Collections.emptyList() : genProject.getBusinessActions();
        velocityContext.put("businessActions", businessActions);
        velocityContext.put("hasBusinessActions", StringUtils.isNotEmpty(businessActions));
        velocityContext.put("frontendPageDesigns", frontendPageDesigns);
        velocityContext.put("frontendPageDesignsByCode", pageDesignsByCode);
        velocityContext.put("frontendPageDesignsByTable", pageDesignsByTable);
        velocityContext.put("hasFrontendPageDesigns", StringUtils.isNotEmpty(frontendPageDesigns));
        velocityContext.put("adminPageDesigns", adminPageDesigns);
        velocityContext.put("adminPageDesignsByCode", adminPageDesignsByCode);
        velocityContext.put("adminPageDesignsByTable", adminPageDesignsByTable);
        velocityContext.put("hasAdminPageDesigns", StringUtils.isNotEmpty(adminPageDesigns));
        StringBuffer createTableSql = new StringBuffer();
        for (GenTable table : tables) {
            createTableSql.append("\n");
            createTableSql.append(table.getCreateTableSql());
            createTableSql.append("\n");
        }
        velocityContext.put("createTableSql", createTableSql.toString());
        return velocityContext;
    }

    @SuppressWarnings("unchecked")
    private static void enrichPageDesign(FrontProjectPageDesign pageDesign) {
        if (pageDesign.getLayout() == null && StringUtils.isNotEmpty(pageDesign.getLayoutJson())) {
            try {
                pageDesign.setLayout(JSON.parseObject(pageDesign.getLayoutJson(), Map.class));
            } catch (RuntimeException e) {
                pageDesign.setLayout(Collections.<String, Object>emptyMap());
            }
        }
        if (pageDesign.getAction() == null && StringUtils.isNotEmpty(pageDesign.getActionJson())) {
            try {
                pageDesign.setAction(JSON.parseObject(pageDesign.getActionJson(), Map.class));
            } catch (RuntimeException e) {
                pageDesign.setAction(Collections.<String, Object>emptyMap());
            }
        }
    }

    private static List<AppPageDesign> mergeFrontendPageDesigns(List<AppPageDesign> pages,
                                                                List<FrontProjectPageDesign> pageDesigns) {
        Map<String, AppPageDesign> pagesByCode = new LinkedHashMap<String, AppPageDesign>();
        if (StringUtils.isNotEmpty(pages)) {
            for (AppPageDesign page : pages) {
                if (page == null || StringUtils.isEmpty(page.getCode())) {
                    continue;
                }
                AppPageDesign copy = copyPageDesign(page);
                pagesByCode.put(copy.getCode(), copy);
            }
        }
        if (StringUtils.isNotEmpty(pageDesigns)) {
            for (FrontProjectPageDesign pageDesign : pageDesigns) {
                if (pageDesign == null || StringUtils.isEmpty(pageDesign.getPageCode())
                        || "admin".equals(pageDesign.getPageScope())) {
                    continue;
                }
                AppPageDesign page = pagesByCode.get(pageDesign.getPageCode());
                if (page == null) {
                    page = new AppPageDesign();
                    page.setCode(pageDesign.getPageCode());
                    pagesByCode.put(page.getCode(), page);
                }
                page.setMenuCode(StringUtils.defaultIfEmpty(pageDesign.getMenuCode(), page.getMenuCode()));
                page.setName(StringUtils.defaultIfEmpty(pageDesign.getPageName(), page.getName()));
                page.setPath(StringUtils.defaultIfEmpty(pageDesign.getRoutePath(), page.getPath()));
                page.setPageType(isFrontendBusinessBlockPageDesign(pageDesign)
                        ? "business_block" : StringUtils.defaultIfEmpty(pageDesign.getPageType(), page.getPageType()));
                page.setTableName(StringUtils.defaultIfEmpty(pageDesign.getTableName(), page.getTableName()));
            }
        }
        return new ArrayList<AppPageDesign>(pagesByCode.values());
    }

    private static AppPageDesign copyPageDesign(AppPageDesign source) {
        AppPageDesign copy = new AppPageDesign();
        copy.setCode(source.getCode());
        copy.setMenuCode(source.getMenuCode());
        copy.setName(source.getName());
        copy.setPath(source.getPath());
        copy.setPageType(source.getPageType());
        copy.setTableName(source.getTableName());
        copy.setParentPageCode(source.getParentPageCode());
        return copy;
    }

    private static boolean isFrontendBusinessBlockPageDesign(FrontProjectPageDesign pageDesign) {
        if (pageDesign == null || StringUtils.isEmpty(pageDesign.getPageCode()) || "admin".equals(pageDesign.getPageScope())) {
            return false;
        }
        if ("business_block".equals(pageDesign.getPageType())) {
            return true;
        }
        return StringUtils.isNotEmpty(pageDesign.getLayoutJson()) && pageDesign.getLayoutJson().contains("business-blocks-v1");
    }

    public static void putPageDesignVelocityContext(VelocityContext context, GenProject project, GenTable table) {
        putPageDesignVelocityContext(context, project, table, "frontend");
    }

    public static void putPageDesignVelocityContext(VelocityContext context, GenProject project, GenTable table, String scope) {
        putPageDesignVelocityContext(context, project, table, scope, null);
    }

    public static void putPageDesignVelocityContext(VelocityContext context, GenProject project, GenTable table,
                                                    String scope, String pageCode) {
        FrontProjectPageDesign pageDesign = findPageDesign(project, table, scope, pageCode);
        putPageDesignDefaults(context);
        context.put("currentFrontendPageDesign", pageDesign);
        context.put("currentPageDesign", pageDesign);
        context.put("hasCurrentFrontendPageDesign", pageDesign != null);
        context.put("hasCurrentPageDesign", pageDesign != null);
        if (pageDesign == null) {
            return;
        }
        enrichPageDesign(pageDesign);
        String canvas = resolvePageDesignCanvas(pageDesign);
        context.put("pageDesignCanvas", canvas);
        context.put("pageDesignIsCardList", canvas.contains("card"));
        context.put("pageDesignIsSplitList", canvas.contains("split"));
        context.put("pageDesignIsCurrentUserList", "current_user_list".equals(pageDesign.getPageType()));
        Map<String, GenTableColumn> columnsByField = buildColumnsByField(table);
        Map<String, Object> queryRegion = findLayoutRegion(pageDesign, "query");
        Map<String, Object> toolbarRegion = findLayoutRegion(pageDesign, "toolbar");
        Map<String, Object> tableRegion = findLayoutRegion(pageDesign, "table");
        Map<String, Object> cardRegion = findLayoutRegion(pageDesign, "cardList");
        if (cardRegion == null) {
            cardRegion = findLayoutRegion(pageDesign, "card");
        }
        Map<String, Object> cardActionsRegion = findLayoutRegion(pageDesign, "cardActions");
        Map<String, Object> formRegion = findLayoutRegion(pageDesign, "dialogForm");
        if (formRegion == null) {
            formRegion = findLayoutRegion(pageDesign, "form");
        }
        List<String> toolbarActionCodes = stringList(toolbarRegion == null ? null : toolbarRegion.get("actions"));
        List<String> rowActionCodes = stringList(tableRegion == null ? null : tableRegion.get("rowActions"));
        List<String> cardActionCodes = stringList(cardActionsRegion == null ? null : cardActionsRegion.get("actions"));
        if (cardActionCodes.isEmpty()) {
            cardActionCodes = rowActionCodes;
        }

        context.put("pageDesignQueryColumns", columnsFromRegion(queryRegion, columnsByField));
        context.put("pageDesignTableColumns", columnsFromRegion(tableRegion, columnsByField));
        context.put("pageDesignFormColumns", columnsFromRegion(formRegion, columnsByField));
        context.put("pageDesignCardColumns", cardColumnsFromRegion(cardRegion, columnsByField));
        context.put("pageDesignCardTitleColumn", columnFromRegionField(cardRegion, "titleField", columnsByField));
        context.put("pageDesignCardImageColumn", columnFromRegionField(cardRegion, "imageField", columnsByField));
        context.put("pageDesignToolbarActionCodes", toolbarActionCodes);
        context.put("pageDesignRowActionCodes", pageDesign.getLayout() != null && canvas.contains("card") ? cardActionCodes : rowActionCodes);
        context.put("pageDesignShowQuery", regionVisible(queryRegion));
        context.put("pageDesignShowToolbar", regionVisible(toolbarRegion));
        context.put("pageDesignShowForm", regionVisible(formRegion));
        context.put("pageDesignAllowAdd", containsAny(toolbarActionCodes, "create", "add"));
        context.put("pageDesignAllowBatchDelete", containsAny(toolbarActionCodes, "batchDelete", "batch_delete"));
        context.put("pageDesignAllowView", containsAny(canvas.contains("card") ? cardActionCodes : rowActionCodes, "view", "detail"));
        context.put("pageDesignAllowEdit", containsAny(canvas.contains("card") ? cardActionCodes : rowActionCodes, "edit", "update"));
        context.put("pageDesignAllowDelete", containsAny(canvas.contains("card") ? cardActionCodes : rowActionCodes, "delete", "remove"));
    }

    private static void putPageDesignDefaults(VelocityContext context) {
        context.put("currentFrontendPageDesign", null);
        context.put("currentPageDesign", null);
        context.put("hasCurrentFrontendPageDesign", false);
        context.put("hasCurrentPageDesign", false);
        context.put("pageDesignCanvas", "frontend-list-v1");
        context.put("pageDesignIsCardList", false);
        context.put("pageDesignIsSplitList", false);
        context.put("pageDesignIsCurrentUserList", false);
        context.put("pageDesignShowQuery", true);
        context.put("pageDesignShowToolbar", true);
        context.put("pageDesignShowForm", true);
        context.put("pageDesignCardColumns", Collections.<GenTableColumn>emptyList());
        context.put("pageDesignCardTitleColumn", null);
        context.put("pageDesignCardImageColumn", null);
        context.put("pageDesignToolbarActionCodes", Collections.<String>emptyList());
        context.put("pageDesignRowActionCodes", Collections.<String>emptyList());
        context.put("pageDesignAllowView", false);
        context.put("pageDesignAllowAdd", false);
        context.put("pageDesignAllowBatchDelete", false);
        context.put("pageDesignAllowEdit", false);
        context.put("pageDesignAllowDelete", false);
        context.put("pageDesignToolbarBusinessActions", Collections.emptyList());
        context.put("hasPageDesignToolbarBusinessActions", false);
        context.put("pageDesignRowBusinessActions", Collections.emptyList());
        context.put("hasPageDesignRowBusinessActions", false);
        context.put("pageDesignBusinessActions", Collections.emptyList());
        context.put("hasPageDesignBusinessActions", false);
        context.put("pageDesignTopBusinessBlocks", Collections.emptyList());
        context.put("pageDesignBottomBusinessBlocks", Collections.emptyList());
        context.put("pageDesignEmbeddedBusinessBlocks", Collections.emptyList());
        context.put("hasPageDesignTopBusinessBlocks", false);
        context.put("hasPageDesignBottomBusinessBlocks", false);
        context.put("hasPageDesignEmbeddedBusinessBlocks", false);
    }

    private static String resolvePageDesignCanvas(FrontProjectPageDesign pageDesign) {
        if (pageDesign == null || pageDesign.getLayout() == null) {
            return "frontend-list-v1";
        }
        Object canvas = pageDesign.getLayout().get("canvas");
        return canvas == null || StringUtils.isEmpty(String.valueOf(canvas))
                ? "frontend-list-v1" : String.valueOf(canvas);
    }

    private static FrontProjectPageDesign findPageDesign(GenProject project, GenTable table, String scope) {
        return findPageDesign(project, table, scope, null);
    }

    private static FrontProjectPageDesign findPageDesign(GenProject project, GenTable table, String scope, String pageCode) {
        if (project == null || table == null || StringUtils.isEmpty(project.getPageDesigns())) {
            return null;
        }
        if (StringUtils.isNotEmpty(pageCode)) {
            for (FrontProjectPageDesign pageDesign : project.getPageDesigns()) {
                if (pageDesign != null && pageDesignScopeMatches(pageDesign, scope)
                        && StringUtils.equals(pageCode, pageDesign.getPageCode())
                        && StringUtils.equals(pageDesign.getTableName(), table.getTableName())) {
                    return pageDesign;
                }
            }
        }
        for (FrontProjectPageDesign pageDesign : project.getPageDesigns()) {
            if (pageDesign == null) {
                continue;
            }
            if (pageDesignScopeMatches(pageDesign, scope) && StringUtils.equals(pageDesign.getTableName(), table.getTableName())) {
                return pageDesign;
            }
        }
        return null;
    }

    private static boolean pageDesignScopeMatches(FrontProjectPageDesign pageDesign, String scope) {
        String expectedScope = "admin".equals(scope) ? "admin" : "frontend";
        String actualScope = StringUtils.isEmpty(pageDesign.getPageScope()) ? "frontend" : pageDesign.getPageScope();
        return expectedScope.equals(actualScope);
    }

    private static Map<String, GenTableColumn> buildColumnsByField(GenTable table) {
        Map<String, GenTableColumn> result = new LinkedHashMap<String, GenTableColumn>();
        if (table == null || StringUtils.isEmpty(table.getColumns())) {
            return result;
        }
        for (GenTableColumn column : table.getColumns()) {
            if (column == null) {
                continue;
            }
            if (StringUtils.isNotEmpty(column.getJavaField())) {
                result.put(column.getJavaField(), column);
            }
            if (StringUtils.isNotEmpty(column.getColumnName())) {
                result.put(column.getColumnName(), column);
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> findLayoutRegion(FrontProjectPageDesign pageDesign, String type) {
        if (pageDesign == null || pageDesign.getLayout() == null) {
            return null;
        }
        Object regionsObject = pageDesign.getLayout().get("regions");
        if (!(regionsObject instanceof List)) {
            return null;
        }
        for (Object regionObject : (List<Object>) regionsObject) {
            if (!(regionObject instanceof Map)) {
                continue;
            }
            Map<String, Object> region = (Map<String, Object>) regionObject;
            Object regionType = region.get("type");
            Object regionId = region.get("id");
            if (type.equals(regionType) || type.equals(regionId)) {
                return region;
            }
        }
        return null;
    }

    private static List<GenTableColumn> columnsFromRegion(Map<String, Object> region, Map<String, GenTableColumn> columnsByField) {
        List<GenTableColumn> columns = new ArrayList<GenTableColumn>();
        if (region == null || columnsByField == null) {
            return columns;
        }
        for (String field : stringList(region.get("fields"))) {
            GenTableColumn column = columnsByField.get(field);
            if (column != null && !columns.contains(column)) {
                columns.add(column);
            }
        }
        return columns;
    }

    private static List<GenTableColumn> cardColumnsFromRegion(Map<String, Object> region, Map<String, GenTableColumn> columnsByField) {
        List<GenTableColumn> columns = columnsFromRegion(region, columnsByField);
        GenTableColumn imageColumn = columnFromRegionField(region, "imageField", columnsByField);
        if (imageColumn != null) {
            columns.remove(imageColumn);
        }
        return columns;
    }

    private static GenTableColumn columnFromRegionField(Map<String, Object> region, String key, Map<String, GenTableColumn> columnsByField) {
        if (region == null || columnsByField == null) {
            return null;
        }
        Object field = region.get(key);
        if (field == null) {
            return null;
        }
        return columnsByField.get(String.valueOf(field));
    }

    private static boolean regionVisible(Map<String, Object> region) {
        if (region == null || !region.containsKey("visible")) {
            return true;
        }
        Object visible = region.get("visible");
        if (visible instanceof Boolean) {
            return ((Boolean) visible).booleanValue();
        }
        return !"false".equalsIgnoreCase(String.valueOf(visible));
    }

    @SuppressWarnings("unchecked")
    private static List<String> stringList(Object value) {
        List<String> result = new ArrayList<String>();
        if (!(value instanceof List)) {
            return result;
        }
        for (Object item : (List<Object>) value) {
            if (item != null) {
                result.add(String.valueOf(item));
            }
        }
        return result;
    }

    private static boolean containsAny(List<String> values, String... expectedValues) {
        if (values == null || values.isEmpty()) {
            return false;
        }
        Set<String> normalized = new HashSet<String>();
        for (String value : values) {
            normalized.add(StringUtils.defaultString(value).toLowerCase());
        }
        for (String expected : expectedValues) {
            if (normalized.contains(StringUtils.defaultString(expected).toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    private static List<Map<String, Object>> buildFrontendNavItems(List<AppMenuDesign> menus,
                                                                   List<AppPageDesign> pages,
                                                                   List<GenTable> tables,
                                                                   Map<String, FrontProjectPageDesign> pageDesignsByCode) {
        List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
        Set<String> paths = new HashSet<String>();
        if (StringUtils.isNotEmpty(menus)) {
            int index = 1;
            for (AppMenuDesign menu : menus) {
                if (menu == null) {
                    continue;
                }
                AppPageDesign page = findMenuPage(menu, pages);
                if (page != null && !shouldShowPageInMenu(page, pageDesignsByCode)) {
                    index++;
                    continue;
                }
                Map<String, Object> item = frontendRouteItemForMenu(menu, page, pages, tables,
                        pageDesignsByCode, index);
                if (item == null) {
                    index++;
                    continue;
                }
                addRouteItemIfAbsent(items, paths, item);
                index++;
            }
        }
        if (StringUtils.isNotEmpty(pages)) {
            int index = 1;
            for (AppPageDesign page : pages) {
                if (!shouldShowPageInMenu(page, pageDesignsByCode)) {
                    index++;
                    continue;
                }
                Map<String, Object> item = frontendRouteItemForPage(page, tables, true, pageDesignsByCode, index);
                addRouteItemIfAbsent(items, paths, item);
                index++;
            }
        }
        if (items.isEmpty() && StringUtils.isEmpty(menus) && StringUtils.isEmpty(pages)) {
            items.addAll(buildTableRouteItems(tables));
        }
        return items;
    }

    private static List<Map<String, Object>> buildFrontendRouteItems(List<Map<String, Object>> navItems,
                                                                     List<AppPageDesign> pages,
                                                                     List<GenTable> tables,
                                                                     Map<String, FrontProjectPageDesign> pageDesignsByCode) {
        List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
        Set<String> paths = new HashSet<String>();
        for (Map<String, Object> navItem : navItems) {
            addRouteItemIfAbsent(items, paths, navItem);
        }
        if (StringUtils.isNotEmpty(pages)) {
            int index = 1;
            for (AppPageDesign page : pages) {
                Map<String, Object> pageItem = frontendRouteItemForPage(page, tables, true, pageDesignsByCode, index);
                addRouteItemIfAbsent(items, paths, pageItem);
                index++;
            }
        }
        for (Map<String, Object> tableItem : buildTableRouteItems(tables)) {
            addRouteItemIfAbsent(items, paths, tableItem);
        }
        return items;
    }

    private static Map<String, Object> frontendRouteItemForMenu(AppMenuDesign menu, AppPageDesign page,
                                                                 List<AppPageDesign> pages, List<GenTable> tables,
                                                                 Map<String, FrontProjectPageDesign> pageDesignsByCode,
                                                                 int index) {
        if (page != null) {
            Map<String, Object> item = frontendRouteItemForPage(page, tables,
                    Boolean.TRUE.equals(menu.getRequiresLogin()), pageDesignsByCode, index);
            if (item != null) {
                item.put("code", sanitizeRouteName(StringUtils.defaultIfEmpty(menu.getCode(), page.getCode()), null));
                item.put("name", StringUtils.defaultIfEmpty(menu.getName(), page.getName()));
                item.put("path", normalizeFrontendPath(StringUtils.defaultIfEmpty(menu.getPath(), page.getPath()),
                        defaultPagePath(page)));
            }
            return item;
        }
        GenTable table = resolveMenuTable(menu, pages, tables);
        if (table == null) {
            return null;
        }
        return frontendRouteItem(
                StringUtils.defaultIfEmpty(menu.getCode(), "frontend_menu_" + index),
                StringUtils.defaultIfEmpty(menu.getName(), table.getFunctionName()),
                normalizeFrontendPath(menu.getPath(), defaultTablePath(table)),
                Boolean.TRUE.equals(menu.getRequiresLogin()),
                table);
    }

    private static Map<String, Object> frontendRouteItemForPage(AppPageDesign page, List<GenTable> tables,
                                                                boolean requiresLogin,
                                                                Map<String, FrontProjectPageDesign> pageDesignsByCode,
                                                                int index) {
        if (page == null) {
            return null;
        }
        if (isBusinessBlockPage(page)) {
            return frontendDesignedPageRouteItem(
                    StringUtils.defaultIfEmpty(page.getCode(), "business_block_" + index),
                    StringUtils.defaultIfEmpty(page.getName(), page.getCode()),
                    normalizeFrontendPath(page.getPath(), defaultBusinessBlockPagePath(page)),
                    requiresLogin,
                    page);
        }
        GenTable table = findTableByName(tables, page.getTableName());
        if (hasPageDesign(page, pageDesignsByCode)) {
            return frontendDesignedPageRouteItem(
                    StringUtils.defaultIfEmpty(page.getCode(), "frontend_page_" + index),
                    StringUtils.defaultIfEmpty(page.getName(), table == null ? page.getCode() : table.getFunctionName()),
                    normalizeFrontendPath(page.getPath(), table == null ? defaultPagePath(page) : defaultTablePath(table)),
                    requiresLogin,
                    page);
        }
        if (table == null) {
            return null;
        }
        return frontendRouteItem(
                StringUtils.defaultIfEmpty(page.getCode(), "frontend_page_" + index),
                StringUtils.defaultIfEmpty(page.getName(), table.getFunctionName()),
                normalizeFrontendPath(page.getPath(), defaultTablePath(table)),
                requiresLogin,
                table);
    }

    private static boolean addRouteItemIfAbsent(List<Map<String, Object>> items, Set<String> paths,
                                                Map<String, Object> item) {
        if (item == null) {
            return false;
        }
        String path = StringUtils.defaultString((String) item.get("path"));
        if (paths.add(path)) {
            items.add(item);
            return true;
        }
        return false;
    }

    private static List<Map<String, Object>> buildTableRouteItems(List<GenTable> tables) {
        List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
        if (StringUtils.isEmpty(tables)) {
            return items;
        }
        int index = 1;
        for (GenTable table : tables) {
            if (table == null) {
                continue;
            }
            items.add(frontendRouteItem(
                    StringUtils.defaultIfEmpty(table.getBusinessName(), "table_" + index),
                    StringUtils.defaultIfEmpty(table.getFunctionName(), table.getBusinessName()),
                    defaultTablePath(table),
                    true,
                    table));
            index++;
        }
        return items;
    }

    private static List<Map<String, Object>> buildFrontendRouteComponents(List<Map<String, Object>> routeItems) {
        List<Map<String, Object>> components = new ArrayList<Map<String, Object>>();
        Set<String> componentNames = new HashSet<String>();
        for (Map<String, Object> routeItem : routeItems) {
            String componentName = (String) routeItem.get("componentName");
            if (StringUtils.isNotEmpty(componentName) && componentNames.add(componentName)) {
                Map<String, Object> component = new LinkedHashMap<String, Object>();
                component.put("componentName", componentName);
                component.put("componentPath", routeItem.get("componentPath"));
                components.add(component);
            }
        }
        return components;
    }

    private static Map<String, Object> frontendRouteItem(String code, String name, String path,
                                                         boolean requiresLogin, GenTable table) {
        Map<String, Object> item = new LinkedHashMap<String, Object>();
        item.put("code", sanitizeRouteName(code, table));
        item.put("name", StringUtils.defaultIfEmpty(name, StringUtils.defaultString(table.getFunctionName())));
        item.put("path", normalizeFrontendPath(path, defaultTablePath(table)));
        item.put("requiresLogin", requiresLogin);
        item.put("componentName", table.getClassName());
        item.put("componentPath", "@/views/" + table.getModuleName() + "/" + table.getBusinessName() + "/index.vue");
        return item;
    }

    private static Map<String, Object> frontendDesignedPageRouteItem(String code, String name, String path,
                                                                     boolean requiresLogin, AppPageDesign page) {
        Map<String, Object> item = new LinkedHashMap<String, Object>();
        String pageCode = StringUtils.defaultIfEmpty(page == null ? null : page.getCode(), "frontend_page");
        item.put("code", sanitizeRouteName(code, null));
        item.put("name", StringUtils.defaultIfEmpty(name, pageCode));
        item.put("path", normalizeFrontendPath(path, defaultPagePath(page)));
        item.put("requiresLogin", requiresLogin);
        item.put("componentName", toProjectClassName(pageCode));
        item.put("componentPath", "@/views/pages/" + pageCode + "/index.vue");
        return item;
    }

    private static boolean hasPageDesign(AppPageDesign page, Map<String, FrontProjectPageDesign> pageDesignsByCode) {
        return page != null && pageDesignsByCode != null && pageDesignsByCode.containsKey(page.getCode());
    }

    private static boolean shouldShowPageInMenu(AppPageDesign page,
                                                Map<String, FrontProjectPageDesign> pageDesignsByCode) {
        if (page == null) {
            return false;
        }
        FrontProjectPageDesign pageDesign = pageDesignsByCode == null ? null : pageDesignsByCode.get(page.getCode());
        Object explicit = navigationValue(pageDesign, "showInMenu");
        if (explicit instanceof Boolean) {
            return ((Boolean) explicit).booleanValue();
        }
        if (explicit != null) {
            return !"false".equalsIgnoreCase(String.valueOf(explicit));
        }
        return !isAssociatedPageType(page.getPageType());
    }

    @SuppressWarnings("unchecked")
    private static Object navigationValue(FrontProjectPageDesign pageDesign, String key) {
        if (pageDesign == null) {
            return null;
        }
        enrichPageDesign(pageDesign);
        if (pageDesign.getLayout() == null) {
            return null;
        }
        Object navigation = pageDesign.getLayout().get("navigation");
        if (!(navigation instanceof Map)) {
            return null;
        }
        return ((Map<String, Object>) navigation).get(key);
    }

    private static AppPageDesign findMenuPage(AppMenuDesign menu, List<AppPageDesign> pages) {
        if (menu == null || StringUtils.isEmpty(pages)) {
            return null;
        }
        for (AppPageDesign page : pages) {
            if (page != null && matchesMenuPage(menu, page)) {
                return page;
            }
        }
        return null;
    }

    private static boolean isBusinessBlockPage(AppPageDesign page) {
        return page != null && "business_block".equals(page.getPageType());
    }

    private static boolean isAssociatedPageType(String pageType) {
        return "detail".equals(pageType) || "form".equals(pageType);
    }

    private static GenTable resolveMenuTable(AppMenuDesign menu, List<AppPageDesign> pages, List<GenTable> tables) {
        if (StringUtils.isEmpty(tables)) {
            return null;
        }
        GenTable table = resolveMenuTableByPage(menu, pages, tables);
        if (table != null) {
            return table;
        }
        String menuKey = normalizeIdentifier(StringUtils.defaultString(menu.getCode()) + " "
                + StringUtils.defaultString(menu.getPath()) + " " + StringUtils.defaultString(menu.getName()));
        for (GenTable candidate : tables) {
            if (matchesMenuTableKey(menuKey, candidate)) {
                return candidate;
            }
        }
        return tables.get(0);
    }

    private static GenTable resolveMenuTableByPage(AppMenuDesign menu, List<AppPageDesign> pages, List<GenTable> tables) {
        if (menu == null || StringUtils.isEmpty(pages)) {
            return null;
        }
        for (AppPageDesign page : pages) {
            if (page == null || !matchesMenuPage(menu, page)) {
                continue;
            }
            GenTable table = findTableByName(tables, page.getTableName());
            if (table != null) {
                return table;
            }
        }
        return null;
    }

    private static boolean matchesMenuPage(AppMenuDesign menu, AppPageDesign page) {
        return StringUtils.isNotEmpty(menu.getPageCode()) && menu.getPageCode().equals(page.getCode())
                || StringUtils.isNotEmpty(menu.getCode()) && menu.getCode().equals(page.getMenuCode());
    }

    private static GenTable findTableByName(List<GenTable> tables, String tableName) {
        String expected = normalizeIdentifier(tableName);
        if (StringUtils.isEmpty(expected)) {
            return null;
        }
        for (GenTable table : tables) {
            if (table == null) {
                continue;
            }
            if (expected.equals(normalizeIdentifier(table.getTableName()))
                    || expected.equals(normalizeIdentifier(table.getBusinessName()))
                    || expected.equals(normalizeIdentifier(table.getClassName()))) {
                return table;
            }
        }
        return null;
    }

    private static boolean matchesMenuTableKey(String menuKey, GenTable table) {
        if (StringUtils.isEmpty(menuKey) || table == null) {
            return false;
        }
        List<String> tableKeys = new ArrayList<String>();
        tableKeys.add(table.getBusinessName());
        tableKeys.add(table.getTableName());
        tableKeys.add(table.getClassName());
        tableKeys.add(table.getFunctionName());
        for (String tableKey : tableKeys) {
            String normalized = normalizeIdentifier(tableKey);
            if (StringUtils.isEmpty(normalized)) {
                continue;
            }
            if (menuKey.contains(normalized) || normalized.contains(menuKey)) {
                return true;
            }
            for (String token : splitIdentifierTokens(tableKey)) {
                if (token.length() >= 4 && menuKey.contains(token)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static List<String> splitIdentifierTokens(String value) {
        String source = StringUtils.defaultString(value).replaceAll("([a-z])([A-Z])", "$1 $2");
        String[] parts = source.split("[^A-Za-z0-9]+");
        List<String> tokens = new ArrayList<String>();
        for (String part : parts) {
            String token = normalizeIdentifier(part);
            if (StringUtils.isNotEmpty(token)) {
                tokens.add(token);
            }
        }
        return tokens;
    }

    private static String resolveDefaultFrontendPath(List<Map<String, Object>> navItems,
                                                     List<Map<String, Object>> routeItems) {
        if (StringUtils.isNotEmpty(navItems)) {
            return (String) navItems.get(0).get("path");
        }
        if (StringUtils.isNotEmpty(routeItems)) {
            return (String) routeItems.get(0).get("path");
        }
        return "/empty";
    }

    private static String defaultTablePath(GenTable table) {
        if (table == null) {
            return "/empty";
        }
        return "/" + StringUtils.defaultString(table.getModuleName()) + "/" + StringUtils.defaultString(table.getBusinessName());
    }

    private static String defaultBusinessBlockPagePath(AppPageDesign page) {
        if (page == null || StringUtils.isEmpty(page.getCode())) {
            return "/business-block";
        }
        return "/" + page.getCode().replace("_", "-");
    }

    private static String defaultPagePath(AppPageDesign page) {
        if (isBusinessBlockPage(page)) {
            return defaultBusinessBlockPagePath(page);
        }
        if (page == null || StringUtils.isEmpty(page.getCode())) {
            return "/page";
        }
        return "/" + page.getCode().replace("_", "-");
    }

    private static String normalizeFrontendPath(String path, String fallback) {
        String value = StringUtils.defaultString(path).trim();
        if (StringUtils.isEmpty(value)) {
            value = fallback;
        }
        if (!value.startsWith("/")) {
            value = "/" + value;
        }
        return value.replaceAll("/{2,}", "/");
    }

    private static String sanitizeRouteName(String code, GenTable table) {
        String value = StringUtils.defaultString(code).replaceAll("[^A-Za-z0-9_]", "_");
        if (StringUtils.isEmpty(value)) {
            value = table == null ? "GeneratedRoute" : StringUtils.defaultString(table.getClassName());
        }
        if (!Character.isLetter(value.charAt(0)) && value.charAt(0) != '_') {
            value = "_" + value;
        }
        return value;
    }

    private static String normalizeIdentifier(String value) {
        return StringUtils.defaultString(value).toLowerCase().replaceAll("[^a-z0-9]", "");
    }

    private static String toProjectClassName(String projectFileName) {
        String source = projectFileName == null ? "" : projectFileName;
        StringBuilder result = new StringBuilder();
        boolean uppercaseNext = true;
        for (int i = 0; i < source.length(); i++) {
            char ch = source.charAt(i);
            if (!Character.isLetterOrDigit(ch)) {
                uppercaseNext = true;
                continue;
            }
            if (result.length() == 0 && !Character.isJavaIdentifierStart(ch)) {
                result.append("App");
            }
            result.append(uppercaseNext ? Character.toUpperCase(ch) : ch);
            uppercaseNext = false;
        }
        if (result.length() == 0) {
            return "GeneratedProject";
        }
        return result.toString();
    }

    public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) {
        String options = genTable.getOptions();
        JSONObject paramsObj = JSON.parseObject(options);
        String parentMenuId = getParentMenuId(paramsObj);
        context.put("parentMenuId", parentMenuId);
    }

    public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) {
        String options = genTable.getOptions();
        JSONObject paramsObj = JSON.parseObject(options);
        String treeCode = getTreecode(paramsObj);
        String treeParentCode = getTreeParentCode(paramsObj);
        String treeName = getTreeName(paramsObj);

        context.put("treeCode", treeCode);
        context.put("treeParentCode", treeParentCode);
        context.put("treeName", treeName);
        context.put("expandColumn", getExpandColumn(genTable));
        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
            context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE));
        }
        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
            context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME));
        }
    }

    public static void setSubVelocityContext(VelocityContext context, GenTable genTable) {
        GenTable subTable = genTable.getSubTable();
        String subTableName = genTable.getSubTableName();
        String subTableFkName = genTable.getSubTableFkName();
        String subClassName = genTable.getSubTable().getClassName();
        String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName);

        context.put("subTable", subTable);
        context.put("subTableName", subTableName);
        context.put("subTableFkName", subTableFkName);
        context.put("subTableFkClassName", subTableFkClassName);
        context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName));
        context.put("subClassName", subClassName);
        context.put("subclassName", StringUtils.uncapitalize(subClassName));
        context.put("subImportList", getImportList(genTable.getSubTable()));
    }

    /**
     * 获取模板信息
     *
     * @return 模板列表
     */
    public static List<String> getTemplateList(String tplCategory, String templatePath) {
        List<String> templates = TemplateUtils.getTemplateList(tplCategory, templatePath);
        return templates;
    }

    /**
     * 获取文件名
     */
    public static String getFileName(String template, GenTable genTable, String templatePath) {
        return FileNameUtils.getFileName(template, genTable, templatePath);
    }

    /**
     * 获取包前缀
     *
     * @param packageName 包名称
     * @return 包前缀名称
     */
    public static String getPackagePrefix(String packageName) {
        int lastIndex = packageName.lastIndexOf(".");
        return StringUtils.substring(packageName, 0, lastIndex);
    }

    /**
     * 根据列类型获取导入包
     *
     * @param genTable 业务表对象
     * @return 返回需要导入的包列表
     */
    public static HashSet<String> getImportList(GenTable genTable) {
        List<GenTableColumn> columns = genTable.getColumns();
        GenTable subGenTable = genTable.getSubTable();
        HashSet<String> importList = new HashSet<String>();
        if (StringUtils.isNotNull(subGenTable)) {
            importList.add("java.util.List");
        }
        for (GenTableColumn column : columns) {
            if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
                importList.add("java.util.Date");
                importList.add("com.fasterxml.jackson.annotation.JsonFormat");
            } else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
                importList.add("java.math.BigDecimal");
            }
        }
        return importList;
    }

    /**
     * 根据列类型获取字典组
     *
     * @param genTable 业务表对象
     * @return 返回字典组
     */
    public static String getDicts(GenTable genTable) {
        List<GenTableColumn> columns = genTable.getColumns();
        Set<String> dicts = new HashSet<String>();
        addDicts(dicts, columns);
        if (StringUtils.isNotNull(genTable.getSubTable())) {
            List<GenTableColumn> subColumns = genTable.getSubTable().getColumns();
            addDicts(dicts, subColumns);
        }
        return StringUtils.join(dicts, ", ");
    }

    /**
     * 添加字典列表
     *
     * @param dicts   字典列表
     * @param columns 列集合
     */
    public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
        for (GenTableColumn column : columns) {
            if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType())
                    && !isQingRemoteDictType(column.getDictType()) && StringUtils.equalsAny(
                    column.getHtmlType(),
                    new String[]{GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX})) {
                dicts.add("'" + column.getDictType() + "'");
            }
        }
    }

    private static List<Map<String, String>> getQingRemoteSelects(GenTable genTable) {
        List<Map<String, String>> remoteSelects = new ArrayList<Map<String, String>>();
        List<GenTableColumn> columns = genTable.getColumns();
        if (columns == null) {
            return remoteSelects;
        }
        for (GenTableColumn column : columns) {
            if (column == null || !isQingRemoteDictType(column.getDictType())) {
                continue;
            }
            Map<String, String> remoteSelect = parseQingRemoteSelect(genTable, column);
            if (!remoteSelect.isEmpty()) {
                remoteSelects.add(remoteSelect);
            }
        }
        return remoteSelects;
    }

    private static Set<String> getQingRemoteSelectFields(List<Map<String, String>> remoteSelects) {
        Set<String> fields = new HashSet<String>();
        for (Map<String, String> remoteSelect : remoteSelects) {
            fields.add(remoteSelect.get("field"));
        }
        return fields;
    }

    private static GenTableColumn getQingCurrentUserColumn(GenTable genTable) {
        if (genTable == null || genTable.getColumns() == null) {
            return null;
        }
        for (GenTableColumn column : genTable.getColumns()) {
            if (column == null) {
                continue;
            }
            if ("userId".equals(column.getJavaField()) || "user_id".equals(column.getColumnName())) {
                return column;
            }
        }
        return null;
    }

    private static Map<String, String> parseQingRemoteSelect(GenTable genTable, GenTableColumn column) {
        Map<String, String> remoteSelect = new LinkedHashMap<String, String>();
        String[] parts = StringUtils.split(column.getDictType(), ":");
        if (parts == null || parts.length < 4) {
            return remoteSelect;
        }
        String remoteTable = parts[1];
        String valueField = StringUtils.toCamelCase(parts[2]);
        String labelField = StringUtils.toCamelCase(parts[3]);
        if (StringUtils.isAnyEmpty(remoteTable, valueField, labelField)) {
            return remoteSelect;
        }
        remoteSelect.put("field", column.getJavaField());
        remoteSelect.put("path", "/" + genTable.getModuleName() + "/" + StringUtils.toCamelCase(remoteTable) + "/page");
        remoteSelect.put("valueField", valueField);
        remoteSelect.put("labelField", labelField);
        return remoteSelect;
    }

    private static boolean isQingRemoteDictType(String dictType) {
        return StringUtils.defaultString(dictType).startsWith("remote:");
    }

    /**
     * 获取权限前缀
     *
     * @param moduleName   模块名称
     * @param businessName 业务名称
     * @return 返回权限前缀
     */
    public static String getPermissionPrefix(String moduleName, String businessName) {
        return StringUtils.format("{}:{}", moduleName, businessName);
    }

    /**
     * 获取上级菜单ID字段
     *
     * @param paramsObj 生成其他选项
     * @return 上级菜单ID字段
     */
    public static String getParentMenuId(JSONObject paramsObj) {
        if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
                && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) {
            return paramsObj.getString(GenConstants.PARENT_MENU_ID);
        }
        return DEFAULT_PARENT_MENU_ID;
    }

    /**
     * 获取树编码
     *
     * @param paramsObj 生成其他选项
     * @return 树编码
     */
    public static String getTreecode(JSONObject paramsObj) {
        if (paramsObj.containsKey(GenConstants.TREE_CODE)) {
            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE));
        }
        return StringUtils.EMPTY;
    }

    /**
     * 获取树父编码
     *
     * @param paramsObj 生成其他选项
     * @return 树父编码
     */
    public static String getTreeParentCode(JSONObject paramsObj) {
        if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE));
        }
        return StringUtils.EMPTY;
    }

    /**
     * 获取树名称
     *
     * @param paramsObj 生成其他选项
     * @return 树名称
     */
    public static String getTreeName(JSONObject paramsObj) {
        if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
            return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME));
        }
        return StringUtils.EMPTY;
    }

    /**
     * 获取需要在哪一列上面显示展开按钮
     *
     * @param genTable 业务表对象
     * @return 展开按钮列序号
     */
    public static int getExpandColumn(GenTable genTable) {
        String options = genTable.getOptions();
        JSONObject paramsObj = JSON.parseObject(options);
        String treeName = paramsObj.getString(GenConstants.TREE_NAME);
        int num = 0;
        for (GenTableColumn column : genTable.getColumns()) {
            if (column.isList()) {
                num++;
                String columnName = column.getColumnName();
                if (columnName.equals(treeName)) {
                    break;
                }
            }
        }
        return num;
    }
}
