package cn.lihongjie.coal;

import cn.lihongjie.coal.annotation.OrgScope;
import cn.lihongjie.coal.annotation.SysLog;
import cn.lihongjie.coal.base.dao.BaseRepository;
import cn.lihongjie.coal.base.dto.CommonDto;
import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.dto.OrgCommonDto;
import cn.lihongjie.coal.base.entity.CommonEntity;
import cn.lihongjie.coal.base.entity.OrgCommonEntity;
import cn.lihongjie.coal.base.mapper.BaseMapper;
import cn.lihongjie.coal.base.mapper.CommonEntityMapper;
import cn.lihongjie.coal.base.mapper.CommonMapper;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.common.ArchiveUtils;
import cn.lihongjie.coal.common.TreeUtils;
import cn.lihongjie.coal.dbFunctions.DbFunctionService;
import cn.lihongjie.coal.exception.BizException;

import com.google.common.base.CaseFormat;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.JavaFormatterOptions;
import com.squareup.javapoet.*;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.OneToMany;

import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.control.DeepClone;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.lang.model.element.Modifier;

/**
 * google-java-format uses internal javac APIs for parsing Java source. The following JVM flags are
 * required when running on JDK 16 and newer, due to JEP 396: Strongly Encapsulate JDK Internals by
 * Default:
 *
 * <p>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
 * --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
 * --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
 * --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
 * --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
 * --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
 */
public class Codegen {

    public static final Path DIRECTORY = Path.of("src/main/java/");

    @SneakyThrows
    public static void main(String[] args) {
        System.out.print("请输入模块名:");
        String moduleName = new Scanner(System.in).nextLine();

        System.out.print("请输入模块中文名:");
        String moduleCNName = new Scanner(System.in).nextLine();

        System.out.print("机构数据[Y]:");
        boolean orgMode =
                Objects.equals(
                        StringUtils.defaultIfBlank(new Scanner(System.in).nextLine(), "Y"), "Y");

        System.out.print("支持归档[Y]:");
        boolean archive =
                Objects.equals(
                        StringUtils.defaultIfBlank(new Scanner(System.in).nextLine(), "Y"), "Y");

        System.out.print("树结构[N]:");
        boolean tree =
                Objects.equals(
                        StringUtils.defaultIfBlank(new Scanner(System.in).nextLine(), "N"), "Y");

        String lModuleName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, moduleName);
        String prefix = Codegen.class.getPackage().getName() + "." + lModuleName;
        String entityPackage = prefix + ".entity";
        String dtoPackage = prefix + ".dto";
        String repoPackage = prefix + ".repository";
        String mapperPackage = prefix + ".mapper";
        String servicePackage = prefix + ".service";
        String controllerPackage = prefix + ".controller";

        // 生成entity

        TypeSpec entity =
                TypeSpec.classBuilder(StringUtils.capitalize(moduleName) + "Entity")
                        .addModifiers(Modifier.PUBLIC)
                        .superclass(orgMode ? OrgCommonEntity.class : CommonEntity.class)
                        .addAnnotation(Data.class)
                        .addAnnotation(Entity.class)
                        .build();

        if (archive) {
            entity =
                    entity.toBuilder()
                            .addField(
                                    FieldSpec.builder(
                                                    String.class, "archiveStatus", Modifier.PRIVATE)
                                            .addAnnotation(
                                                    AnnotationSpec.builder(
                                                                    org.hibernate.annotations
                                                                            .Comment.class)
                                                            .addMember("value", "$S", "归档状态")
                                                            .build())
                                            .addAnnotation(
                                                    AnnotationSpec.builder(
                                                                    org.hibernate.annotations
                                                                            .ColumnDefault.class)
                                                            .addMember("value", "$S", "'0'")
                                                            .build())
                                            .initializer("$S", "0")
                                            .build())
                            .addField(
                                    FieldSpec.builder(
                                                    String.class,
                                                    "archiveStatusName",
                                                    Modifier.PRIVATE)
                                            .addAnnotation(
                                                    AnnotationSpec.builder(
                                                                    org.hibernate.annotations
                                                                            .Formula.class)
                                                            .addMember(
                                                                    "value",
                                                                    "$S",
                                                                    "(select i.name\n"
                                                                            + "from t_dictionary d,\n"
                                                                            + "     t_dictionary_item i\n"
                                                                            + "where d.id = i.dictionary_id\n"
                                                                            + "  and d.code = 'archiveStatus'\n"
                                                                            + "  and i.code = archive_status)")
                                                            .build())
                                            .build())
                            .build();
        }

        if (tree) {
            entity =
                    entity.toBuilder()
                            .addField(
                                    FieldSpec.builder(
                                                    ClassName.get(entityPackage, entity.name),
                                                    "parent",
                                                    Modifier.PRIVATE)
                                            .addAnnotation(
                                                    AnnotationSpec.builder(
                                                                    jakarta.persistence.ManyToOne
                                                                            .class)
                                                            .build())
                                            .build())
                            .addField(
                                    FieldSpec.builder(
                                                    ParameterizedTypeName.get(
                                                            ClassName.get(List.class),
                                                            ClassName.get(
                                                                    entityPackage, entity.name)),
                                                    "children",
                                                    Modifier.PRIVATE)
                                            .addAnnotation(
                                                    AnnotationSpec.builder(OneToMany.class)
                                                            .addMember("mappedBy", "$S", "parent")
                                                            .addMember(
                                                                    "cascade",
                                                                    "$T.ALL",
                                                                    CascadeType.class)
                                                            .build())
                                            .build())
                            .build();
        }

        saveFile(entityPackage, entity);

        // 生成dto
        TypeSpec dto =
                TypeSpec.classBuilder(StringUtils.capitalize(moduleName) + "Dto")
                        .addModifiers(Modifier.PUBLIC)
                        .superclass(orgMode ? OrgCommonDto.class : CommonDto.class)
                        .addAnnotation(Data.class)
                        .build();

        if (archive) {
            dto =
                    dto.toBuilder()
                            .addField(
                                    FieldSpec.builder(
                                                    String.class, "archiveStatus", Modifier.PRIVATE)
                                            .build())
                            .addField(
                                    FieldSpec.builder(
                                                    String.class,
                                                    "archiveStatusName",
                                                    Modifier.PRIVATE)
                                            .build())
                            .build();
        }

        if (tree) {
            dto =
                    dto.toBuilder()
                            .addField(
                                    FieldSpec.builder(String.class, "parent", Modifier.PRIVATE)
                                            .build())
                            .build();
        }

        saveFile(dtoPackage, dto);

        // 生成createDto
        TypeSpec createDto =
                TypeSpec.classBuilder("Create" + StringUtils.capitalize(moduleName) + "Dto")
                        .addModifiers(Modifier.PUBLIC)
                        .superclass(orgMode ? OrgCommonDto.class : CommonDto.class)
                        .addAnnotation(Data.class)
                        .build();

        if (tree) {
            createDto =
                    createDto.toBuilder()
                            .addField(
                                    FieldSpec.builder(String.class, "parent", Modifier.PRIVATE)
                                            .build())
                            .build();
        }

        saveFile(dtoPackage, createDto);

        // 生成updateDto
        TypeSpec updateDto =
                TypeSpec.classBuilder("Update" + StringUtils.capitalize(moduleName) + "Dto")
                        .addModifiers(Modifier.PUBLIC)
                        .superclass(orgMode ? OrgCommonDto.class : CommonDto.class)
                        .addAnnotation(Data.class)
                        .build();

        if (tree) {
            updateDto =
                    updateDto.toBuilder()
                            .addField(
                                    FieldSpec.builder(String.class, "parent", Modifier.PRIVATE)
                                            .build())
                            .build();
        }
        saveFile(dtoPackage, updateDto);

        TypeSpec treeDto = null;
        if (tree) {
            // 生成treeDto
            treeDto =
                    TypeSpec.classBuilder(StringUtils.capitalize(moduleName) + "TreeDto")
                            .addModifiers(Modifier.PUBLIC)
                            .superclass(orgMode ? OrgCommonDto.class : CommonDto.class)
                            .addAnnotation(Data.class)
                            .build();

            treeDto =
                    treeDto.toBuilder()
                            .addField(
                                    FieldSpec.builder(
                                                    ParameterizedTypeName.get(
                                                            ClassName.get(List.class),
                                                            ClassName.get(
                                                                    dtoPackage, treeDto.name)),
                                                    "children",
                                                    Modifier.PRIVATE)
                                            .build())
                            .addField(
                                    FieldSpec.builder(String.class, "parent", Modifier.PRIVATE)
                                            .build())
                            .build();
            saveFile(dtoPackage, treeDto);
        }

        // 生成repository
        TypeSpec repository =
                TypeSpec.interfaceBuilder(StringUtils.capitalize(moduleName) + "Repository")
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(Repository.class)
                        .addSuperinterface(
                                ParameterizedTypeName.get(
                                        ClassName.get(BaseRepository.class),
                                        ClassName.get(entityPackage, entity.name)))
                        .build();

        saveFile(repoPackage, repository);

        // 生成mapper
        TypeSpec mapper =
                TypeSpec.interfaceBuilder(StringUtils.capitalize(moduleName) + "Mapper")
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(
                                AnnotationSpec.builder(Mapper.class)
                                        .addMember(
                                                "componentModel",
                                                "org.mapstruct.MappingConstants.ComponentModel.SPRING",
                                                "")
                                        .addMember(
                                                "uses",
                                                "{$T.class, $T.class}",
                                                CommonMapper.class,
                                                CommonEntityMapper.class)
                                        .addMember("mappingControl", "$T.class", DeepClone.class)
                                        .build())
                        .addSuperinterface(
                                ParameterizedTypeName.get(
                                        ClassName.get(BaseMapper.class),
                                        ClassName.get(entityPackage, entity.name),
                                        ClassName.get(dtoPackage, dto.name),
                                        ClassName.get(dtoPackage, createDto.name),
                                        ClassName.get(dtoPackage, updateDto.name)))
                        .build();

        if (tree) {
            mapper =
                    mapper.toBuilder()
                            .addMethod(
                                    MethodSpec.methodBuilder("toTreeDto")
                                            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
                                            .addAnnotation(
                                                    AnnotationSpec.builder(Mappings.class)
                                                            .addMember(
                                                                    "value",
                                                                    "{@$T(target = \"children\", qualifiedByName = \"toTreeDto\")}",
                                                                    Mapping.class)
                                                            .build())
                                            .addAnnotation(
                                                    AnnotationSpec.builder(Named.class)
                                                            .addMember("value", "$S", "toTreeDto")
                                                            .build())
                                            .returns(ClassName.get(dtoPackage, treeDto.name))
                                            .addParameter(
                                                    ClassName.get(entityPackage, entity.name),
                                                    "entity")
                                            .build())
                            .addMethod(
                                    MethodSpec.methodBuilder("toTreeDtoExcludeChildren")
                                            .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
                                            .addAnnotation(
                                                    AnnotationSpec.builder(Mappings.class)
                                                            .addMember(
                                                                    "value",
                                                                    "{@$T(target = \"children\",ignore=true)}",
                                                                    Mapping.class)
                                                            .build())
                                            .returns(ClassName.get(dtoPackage, treeDto.name))
                                            .addParameter(
                                                    ClassName.get(entityPackage, entity.name),
                                                    "entity")
                                            .build())
                            .build();
        }

        saveFile(mapperPackage, mapper);

        // 生成service
        MethodSpec serviceUpdate =
                MethodSpec.methodBuilder("update")
                        .addModifiers(Modifier.PUBLIC)
                        .returns(ClassName.get(dtoPackage, dto.name))
                        .addParameter(ClassName.get(dtoPackage, updateDto.name), "request")
                        .addStatement(
                                """
                                $T entity = this.repository.get(request.getId());
                                this.mapper.updateEntity(entity, request);

                                this.repository.save(entity);

                                return getById(entity.getId())
                                                    """,
                                ClassName.get(entityPackage, entity.name))
                        .build();

        if (archive) {
            serviceUpdate =
                    MethodSpec.methodBuilder("update")
                            .addModifiers(Modifier.PUBLIC)
                            .returns(ClassName.get(dtoPackage, dto.name))
                            .addParameter(ClassName.get(dtoPackage, updateDto.name), "request")
                            .addStatement(
                                    """
                                    $T entity = this.repository.get(request.getId());
                                     $T.checkArchiveStatus(
                                                    entity,
                                                    $T::getArchiveStatus,
                                                    x -> "0",
                                                    (e, actual, expected) -> {
                                                        throw new $T("数据 " + "已归档，无法编辑");
                                                    });
                                    this.mapper.updateEntity(entity, request);

                                    this.repository.save(entity);

                                    return getById(entity.getId())
                                                        """,
                                    ClassName.get(entityPackage, entity.name),
                                    ClassName.get(ArchiveUtils.class),
                                    ClassName.get(entityPackage, entity.name),
                                    ClassName.get(BizException.class))
                            .build();
        }

        MethodSpec serviceDelete =
                MethodSpec.methodBuilder("delete")
                        .addModifiers(Modifier.PUBLIC)
                        .returns(void.class)
                        .addParameter(ClassName.get(IdRequest.class), "request")
                        .addStatement(
                                """
                                this.repository.deleteAllById(request.getIds())
                                                    """)
                        .build();

        if (archive) {
            serviceDelete =
                    MethodSpec.methodBuilder("delete")
                            .addModifiers(Modifier.PUBLIC)
                            .returns(void.class)
                            .addParameter(ClassName.get(IdRequest.class), "request")
                            .addStatement(
                                    """
                                    $T.checkArchiveStatus(
                                            this.repository::findAllById,
                                            request.getIds(),
                                            $T::getArchiveStatus,
                                            x -> "0",
                                            (e, actual, expected) -> {
                                                throw new $T("数据 " + "已归档，无法删除");
                                            });
                                    this.repository.deleteAllById(request.getIds())
                                                        """,
                                    ClassName.get(ArchiveUtils.class),
                                    ClassName.get(entityPackage, entity.name),
                                    ClassName.get(BizException.class))
                            .build();
        }

        TypeSpec service =
                TypeSpec.classBuilder(StringUtils.capitalize(moduleName) + "Service")
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(Service.class)
                        .addAnnotation(Slf4j.class)
                        .addAnnotation(Transactional.class)
                        .superclass(
                                ParameterizedTypeName.get(
                                        ClassName.get(BaseService.class),
                                        ClassName.get(entityPackage, entity.name),
                                        ClassName.get(repoPackage, repository.name)))
                        .addField(
                                FieldSpec.builder(
                                                ClassName.get(repoPackage, repository.name),
                                                "repository",
                                                Modifier.PRIVATE)
                                        .addAnnotation(Autowired.class)
                                        .build())
                        .addField(
                                FieldSpec.builder(
                                                ClassName.get(mapperPackage, mapper.name),
                                                "mapper",
                                                Modifier.PRIVATE)
                                        .addAnnotation(Autowired.class)
                                        .build())
                        .addField(
                                FieldSpec.builder(
                                                ClassName.get(ConversionService.class),
                                                "conversionService",
                                                Modifier.PRIVATE)
                                        .addAnnotation(Autowired.class)
                                        .build())
                        .addField(
                                FieldSpec.builder(
                                                ClassName.get(DbFunctionService.class),
                                                "dbFunctionService",
                                                Modifier.PRIVATE)
                                        .addAnnotation(Autowired.class)
                                        .build())
                        .addMethod(
                                MethodSpec.methodBuilder("create")
                                        .addModifiers(Modifier.PUBLIC)
                                        .returns(ClassName.get(dtoPackage, dto.name))
                                        .addParameter(
                                                ClassName.get(dtoPackage, createDto.name),
                                                "request")
                                        .addStatement(
                                                """
            $T entity = mapper.toEntity(request);


            this.repository.save(entity);
            return getById(entity.getId())
                                """,
                                                ClassName.get(entityPackage, entity.name))
                                        .build())
                        .addMethod(serviceUpdate)
                        .addMethod(serviceDelete)
                        .addMethod(
                                MethodSpec.methodBuilder("getById")
                                        .addModifiers(Modifier.PUBLIC)
                                        .returns(ClassName.get(dtoPackage, dto.name))
                                        .addParameter(ClassName.get(String.class), "id")
                                        .addStatement(
                                                """
            $T entity = repository.get(id);


            return mapper.toDto(entity)
                                """,
                                                ClassName.get(entityPackage, entity.name))
                                        .build())
                        .addMethod(
                                MethodSpec.methodBuilder("list")
                                        .addModifiers(Modifier.PUBLIC)
                                        .returns(
                                                ParameterizedTypeName.get(
                                                        ClassName.get(Page.class),
                                                        ClassName.get(dtoPackage, dto.name)))
                                        .addParameter(ClassName.get(CommonQuery.class), "query")
                                        .addStatement(
                                                """
             $T<$T> page = repository.findAll(query.specification(conversionService), $T.of(query.getPageNo(), query.getPageSize(), $T.by(query.getOrders())));


            return page.map(this.mapper::toDto)
                                """,
                                                ClassName.get(Page.class),
                                                ClassName.get(entityPackage, entity.name),
                                                ClassName.get(PageRequest.class),
                                                ClassName.get(Sort.class))
                                        .build())
                        .build();

        if (archive) {
            service =
                    service.toBuilder()
                            .addMethod(
                                    MethodSpec.methodBuilder("archive")
                                            .addModifiers(Modifier.PUBLIC)
                                            .returns(void.class)
                                            .addParameter(ClassName.get(IdRequest.class), "dto")
                                            .addStatement(
                                                    """
       this.repository.findAllById(dto.getIds()).stream()
                .peek(x -> $T.checkArchiveStatus(x, $T::getArchiveStatus, y -> "0", (e, actual, expected) -> {
                    throw new $T("数据 " + "已归档，无法再次归档");
                }))
                .peek(x -> x.setArchiveStatus("1"))
                .forEach(this.repository::save);
                                """,
                                                    ClassName.get(ArchiveUtils.class),
                                                    ClassName.get(entityPackage, entity.name),
                                                    ClassName.get(BizException.class))
                                            .build())
                            .addMethod(
                                    MethodSpec.methodBuilder("unarchive")
                                            .addModifiers(Modifier.PUBLIC)
                                            .returns(void.class)
                                            .addParameter(ClassName.get(IdRequest.class), "dto")
                                            .addStatement(
                                                    """
                this.repository.findAllById(dto.getIds()).stream()
                        .peek(x->$T.checkArchiveStatus(x, $T::getArchiveStatus, y -> "1", (e, actual, expected) -> {
                            throw new $T("数据" + "未归档，无法取消归档");
                        }))
                        .peek(x -> x.setArchiveStatus("0"))
                        .forEach(this.repository::save);
        """,
                                                    ClassName.get(ArchiveUtils.class),
                                                    ClassName.get(entityPackage, entity.name),
                                                    ClassName.get(BizException.class))
                                            .build())
                            .build();
        }

        if (tree) {
            service =
                    service.toBuilder()
                            .addMethod(
                                    MethodSpec.methodBuilder("getRoots")
                                            .addModifiers(Modifier.PUBLIC)
                                            .addParameter(
                                                    ClassName.get(CommonQuery.class), "request")
                                            .returns(
                                                    ParameterizedTypeName.get(
                                                            ClassName.get(List.class),
                                                            ClassName.get(
                                                                    dtoPackage, treeDto.name)))
                                            .addCode(
                                                    CodeBlock.builder()
                                                            .add(
                                                                    """

        if ($T.isEmpty(request.getItems())){
            $T roots = this.repository.findAll( (root, query, criteriaBuilder) -> criteriaBuilder.isNull(root.get("parent")));
            return roots.stream().map(de -> this.mapper.toTreeDto(de)).collect($T.toList());
        }else {
            $T page =
                    repository.findAll(
                            request.specification(conversionService),
                            $T.of(
                                    request.getPageNo(),
                                    request.getPageSize(),
                                    $T.by(request.getOrders())));

            $T selfAndParentIds =
                    this.dbFunctionService.selfAndParentIds(dbFunctionService.entityToTableName($T.class),
                            page.stream().map(x -> x.getId()).collect($T.toList()), true);

            $T selfAndParent = this.findAllByIds(selfAndParentIds).stream().map(x -> (this.mapper.toTreeDtoExcludeChildren(x))).collect($T.toList());


            return $T.stream(
                            $T.buildTreeFromList(
                                            selfAndParent,
                                            $T::getId,
                                            x -> x.getParent(),
                                            (x, y) -> {
                                                if (x.getChildren() == null) {
                                                    x.setChildren(new $T<>());
                                                }
                                                x.getChildren().add(y);
                                                return null;
                                            })
                                    .spliterator(),
                            false)
                    .collect($T.toList());
        }




           """,
                                                                    ClassName.get(
                                                                            CollectionUtils.class),
                                                                    ParameterizedTypeName.get(
                                                                            ClassName.get(
                                                                                    List.class),
                                                                            ClassName.get(
                                                                                    entityPackage,
                                                                                    entity.name)),
                                                                    ClassName.get(Collectors.class),
                                                                    ParameterizedTypeName.get(
                                                                            ClassName.get(
                                                                                    Page.class),
                                                                            ClassName.get(
                                                                                    entityPackage,
                                                                                    entity.name)),
                                                                    ClassName.get(
                                                                            PageRequest.class),
                                                                    ClassName.get(Sort.class),
                                                                    ParameterizedTypeName.get(
                                                                            ClassName.get(
                                                                                    List.class),
                                                                            ClassName.get(
                                                                                    String.class)),
                                                                    ClassName.get(
                                                                            entityPackage,
                                                                            entity.name),
                                                                    ClassName.get(Collectors.class),
                                                                    ParameterizedTypeName.get(
                                                                            ClassName.get(
                                                                                    List.class),
                                                                            ClassName.get(
                                                                                    dtoPackage,
                                                                                    treeDto.name)),
                                                                    ClassName.get(Collectors.class),
                                                                    ClassName.get(
                                                                            StreamSupport.class),
                                                                    ClassName.get(TreeUtils.class),
                                                                    ClassName.get(
                                                                            dtoPackage,
                                                                            treeDto.name),
                                                                    ClassName.get(ArrayList.class),
                                                                    ClassName.get(Collectors.class))
                                                            .build())
                                            .build())
                            .addMethod(
                                    MethodSpec.methodBuilder("getTreeByIds")
                                            .addModifiers(Modifier.PUBLIC)
                                            .returns(
                                                    ParameterizedTypeName.get(
                                                            ClassName.get(List.class),
                                                            ClassName.get(
                                                                    dtoPackage, treeDto.name)))
                                            .addParameter(ClassName.get(IdRequest.class), "request")
                                            .addStatement(
                                                    """
                if (request.getIds().isEmpty()) {
                       return new $T<>();
                   }
                   var roots = this.repository.findAll((root, query, criteriaBuilder) -> root.get("id").in(request.getIds()));
                   return roots.stream().map(de -> this.mapper.toTreeDto(de)).collect($T.toList())
           """,
                                                    ClassName.get(ArrayList.class),
                                                    ClassName.get(Collectors.class))
                                            .build())
                            .build();
        }

        saveFile(servicePackage, service);

        // 生成controller
        TypeSpec.Builder controllerBuilder =
                TypeSpec.classBuilder(StringUtils.capitalize(moduleName) + "Controller")
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(RestController.class)
                        .addAnnotation(
                                AnnotationSpec.builder(RequestMapping.class)
                                        .addMember("value", "$S", "/" + lModuleName)
                                        .build())
                        .addAnnotation(
                                AnnotationSpec.builder(SysLog.class)
                                        .addMember(
                                                "module",
                                                "$S",
                                                StringUtils.defaultIfBlank(moduleCNName, ""))
                                        .build())
                        .addAnnotation(Slf4j.class);

        if (orgMode) {
            controllerBuilder.addAnnotation(OrgScope.class);
        }
        controllerBuilder
                .addField(
                        FieldSpec.builder(
                                        ClassName.get(servicePackage, service.name),
                                        "service",
                                        Modifier.PRIVATE)
                                .addAnnotation(Autowired.class)
                                .build())
                .addMethod(
                        MethodSpec.methodBuilder("create")
                                .addModifiers(Modifier.PUBLIC)
                                .returns(ClassName.get(dtoPackage, dto.name))
                                .addAnnotation(
                                        AnnotationSpec.builder(PostMapping.class)
                                                .addMember("value", "$S", "/create")
                                                .build())
                                .addParameter(
                                        ParameterSpec.builder(
                                                        ClassName.get(dtoPackage, createDto.name),
                                                        "request")
                                                .addAnnotation(RequestBody.class)
                                                .build())
                                .addStatement(
                                        """
                                return this.service.create(request)
                                                    """)
                                .build())
                .addMethod(
                        MethodSpec.methodBuilder("update")
                                .addModifiers(Modifier.PUBLIC)
                                .returns(ClassName.get(dtoPackage, dto.name))
                                .addAnnotation(
                                        AnnotationSpec.builder(PostMapping.class)
                                                .addMember("value", "$S", "/update")
                                                .build())
                                .addParameter(
                                        ParameterSpec.builder(
                                                        ClassName.get(dtoPackage, updateDto.name),
                                                        "request")
                                                .addAnnotation(RequestBody.class)
                                                .build())
                                .addStatement(
                                        """
                                return this.service.update(request)
                                                    """)
                                .build())
                .addMethod(
                        MethodSpec.methodBuilder("delete")
                                .addModifiers(Modifier.PUBLIC)
                                .returns(Object.class)
                                .addAnnotation(
                                        AnnotationSpec.builder(PostMapping.class)
                                                .addMember("value", "$S", "/delete")
                                                .build())
                                .addParameter(
                                        ParameterSpec.builder(
                                                        ClassName.get(IdRequest.class), "request")
                                                .addAnnotation(RequestBody.class)
                                                .build())
                                .addStatement(
                                        """
                                this.service.delete(request);
                                return true
                                                                     """)
                                .build())
                .addMethod(
                        MethodSpec.methodBuilder("getById")
                                .addModifiers(Modifier.PUBLIC)
                                .returns(ClassName.get(dtoPackage, dto.name))
                                .addAnnotation(
                                        AnnotationSpec.builder(PostMapping.class)
                                                .addMember("value", "$S", "/getById")
                                                .build())
                                .addParameter(
                                        ParameterSpec.builder(
                                                        ClassName.get(IdRequest.class), "request")
                                                .addAnnotation(RequestBody.class)
                                                .build())
                                .addStatement(
                                        """
                                return this.service.getById(request.getId())
                                                    """)
                                .build())
                .addMethod(
                        MethodSpec.methodBuilder("list")
                                .addModifiers(Modifier.PUBLIC)
                                .returns(
                                        ParameterizedTypeName.get(
                                                ClassName.get(Page.class),
                                                ClassName.get(dtoPackage, dto.name)))
                                .addAnnotation(
                                        AnnotationSpec.builder(PostMapping.class)
                                                .addMember("value", "$S", "/list")
                                                .build())
                                .addParameter(
                                        ParameterSpec.builder(
                                                        ClassName.get(CommonQuery.class), "request")
                                                .addAnnotation(RequestBody.class)
                                                .build())
                                .addStatement(
                                        """
                                        return this.service.list(request)
                                                                                                  """,
                                        ClassName.get(Page.class),
                                        ClassName.get(entityPackage, entity.name),
                                        ClassName.get(PageRequest.class),
                                        ClassName.get(Sort.class))
                                .build());

        if (archive) {
            controllerBuilder
                    .addMethod(
                            MethodSpec.methodBuilder("archive")
                                    .addModifiers(Modifier.PUBLIC)
                                    .returns(Object.class)
                                    .addAnnotation(
                                            AnnotationSpec.builder(PostMapping.class)
                                                    .addMember("value", "$S", "/archive")
                                                    .build())
                                    .addParameter(
                                            ParameterSpec.builder(
                                                            ClassName.get(IdRequest.class),
                                                            "request")
                                                    .addAnnotation(RequestBody.class)
                                                    .build())
                                    .addStatement(
                                            """
                                this.service.archive(request);
                                return true
                                                                     """)
                                    .build())
                    .addMethod(
                            MethodSpec.methodBuilder("unarchive")
                                    .addModifiers(Modifier.PUBLIC)
                                    .returns(Object.class)
                                    .addAnnotation(
                                            AnnotationSpec.builder(PostMapping.class)
                                                    .addMember("value", "$S", "/unarchive")
                                                    .build())
                                    .addParameter(
                                            ParameterSpec.builder(
                                                            ClassName.get(IdRequest.class),
                                                            "request")
                                                    .addAnnotation(RequestBody.class)
                                                    .build())
                                    .addStatement(
                                            """
                                this.service.unarchive(request);
                                return true
                                                                     """)
                                    .build());
        }

        if (tree) {
            controllerBuilder
                    .addMethod(
                            MethodSpec.methodBuilder("roots")
                                    .addModifiers(Modifier.PUBLIC)
                                    .addParameter(
                                            ParameterSpec.builder(
                                                            ClassName.get(CommonQuery.class),
                                                            "request")
                                                    .addAnnotation(RequestBody.class)
                                                    .build())
                                    .returns(
                                            ParameterizedTypeName.get(
                                                    ClassName.get(List.class),
                                                    ClassName.get(dtoPackage, treeDto.name)))
                                    .addAnnotation(
                                            AnnotationSpec.builder(PostMapping.class)
                                                    .addMember("value", "$S", "/roots")
                                                    .build())
                                    .addStatement(
                                            """
             return this.service.getRoots(request)
           """)
                                    .build())
                    .addMethod(
                            MethodSpec.methodBuilder("treeByIds")
                                    .addModifiers(Modifier.PUBLIC)
                                    .returns(
                                            ParameterizedTypeName.get(
                                                    ClassName.get(List.class),
                                                    ClassName.get(dtoPackage, treeDto.name)))
                                    .addAnnotation(
                                            AnnotationSpec.builder(PostMapping.class)
                                                    .addMember("value", "$S", "/treeByIds")
                                                    .build())
                                    .addParameter(
                                            ParameterSpec.builder(
                                                            ClassName.get(IdRequest.class),
                                                            "request")
                                                    .addAnnotation(RequestBody.class)
                                                    .build())
                                    .addStatement(
                                            """
                return this.service.getTreeByIds(request)
           """)
                                    .build());
        }

        saveFile(controllerPackage, controllerBuilder.build());

        if (tree) {
            genTreeScript(lModuleName, controllerPackage, controllerBuilder.build());
        } else {
            genEnumScript(lModuleName, controllerPackage, controllerBuilder.build());
        }
    }

    @SneakyThrows
    private static void genTreeScript(
            String lModuleName, String controllerPackage, TypeSpec controller) {
        var script =
                """

    package scripts.dict

    import cn.lihongjie.coal.base.dto.CommonQuery
    import %s
    import org.springframework.context.ApplicationContext

    ApplicationContext ioc = ioc;

    def controller = ioc.getBean(%s.class)




    return controller.roots(new CommonQuery())


    """
                        .formatted(controllerPackage + "." + controller.name, controller.name);

        Files.writeString(
                Path.of(
                        "src/main/resources/scripts/dict/tree/%sTree.groovy"
                                .formatted(lModuleName)),
                script);
    }

    @SneakyThrows
    private static void genEnumScript(
            String lModuleName, String controllerPackage, TypeSpec controller) {

        var script =
                """

    package scripts.dict

    import cn.lihongjie.coal.base.dto.CommonQuery
    import %s
    import org.springframework.context.ApplicationContext

    ApplicationContext ioc = ioc;

    def controller = ioc.getBean(%s.class)




    return controller.list(new CommonQuery())


    """
                        .formatted(controllerPackage + "." + controller.name, controller.name);

        Files.writeString(
                Path.of(
                        "src/main/resources/scripts/dict/enum/%sDict.groovy"
                                .formatted(lModuleName)),
                script);
    }

    @SneakyThrows
    public static void saveFile(String pkg, TypeSpec entity) throws IOException {

        JavaFile javaFile = JavaFile.builder(pkg, entity).build();

        Path outputDirectory = DIRECTORY;
        if (!javaFile.packageName.isEmpty()) {
            for (String packageComponent : javaFile.packageName.split("\\.")) {
                outputDirectory = outputDirectory.resolve(packageComponent);
            }
            Files.createDirectories(outputDirectory);
        }

        Path outputPath = outputDirectory.resolve(javaFile.typeSpec.name + ".java");
        try (Writer writer =
                new OutputStreamWriter(Files.newOutputStream(outputPath), StandardCharsets.UTF_8)) {

            StringWriter out = new StringWriter();
            javaFile.writeTo(out);

            String formattedSource =
                    new Formatter(
                                    JavaFormatterOptions.builder()
                                            .style(JavaFormatterOptions.Style.AOSP)
                                            .formatJavadoc(true)
                                            .reorderModifiers(true)
                                            .build())
                            .formatSource(out.toString());

            writer.write(formattedSource);
        }
    }
}
