package com.eactive.eai.rms.onl.transaction.apim;

import com.eactive.apim.portal.apispec.entity.ApiSpecInfo;
import com.eactive.apim.portal.apispec.service.ApiSpecInfoService;
import com.eactive.eai.data.entity.onl.transformer.layout.LayoutEntity;
import com.eactive.eai.rms.data.entity.onl.layout.LayoutService;
import com.eactive.eai.rms.onl.common.exception.BizException;
import com.eactive.eai.rms.onl.manage.rule.layout.mapper.LayoutUIMapper;
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutItemUI;
import com.eactive.eai.rms.onl.manage.rule.layout.ui.LayoutUI;
import com.eactive.eai.rms.onl.transaction.apim.mapping.ApiSpecUIMapper;
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiInterfaceUI;
import com.eactive.eai.rms.onl.transaction.apim.ui.ApiSpecInfoUI;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class ApiSpecManService {

    @Autowired
    private ApiSpecInfoService apiSpecInfoService;

    @Autowired
    private ApiSpecUIMapper apiSpecUIMapper;

    @Autowired
    private LayoutService layoutService;

    @Autowired
    private LayoutUIMapper layoutUIMapper;

    private final ObjectMapper objectMapper = new ObjectMapper();

    public ApiSpecInfoUI selectDetail(String eaiSvcName) {
        Optional<ApiSpecInfo> apiSpecInfo = apiSpecInfoService.findById(eaiSvcName);
        return apiSpecUIMapper.map(apiSpecInfo);
    }

    public void delete(ApiSpecInfoUI apiSpecInfoUI) {
        if (apiSpecInfoUI.getApiId() == null) {
            throw new BizException("API specification ID is required for deletion");
        }

        apiSpecInfoService.deleteById(apiSpecInfoUI.getApiId());
    }

    public void save(ApiSpecInfoUI apiSpecInfoUI) {
        if (StringUtils.isEmpty(apiSpecInfoUI.getApiId())) {
            throw new BizException("Failed to insert API specification");
        }

        ApiSpecInfo apiSpecInfo = apiSpecUIMapper.mapToEntity(apiSpecInfoUI);

        Optional<ApiSpecInfo> optSpec = apiSpecInfoService.findById(apiSpecInfo.getApiId());
        if (optSpec.isPresent()) {
            ApiSpecInfo updatedApiSpec = apiSpecInfoService.updateById(apiSpecInfo.getApiId(), apiSpecInfo);
            if (updatedApiSpec == null) {
                throw new BizException("Failed to update API specification. Specification not found.");
            }
        } else {
            apiSpecInfoService.create(apiSpecInfo);
        }
    }

    public LayoutUI selectLayoutUI(String layoutName){
        // Layout 서비스를 통해 Layout 정보 조회
        LayoutEntity layoutEntity = layoutService.getById(layoutName);
        // layout detail 조회
        LayoutUI layoutUI = layoutUIMapper.toVo(layoutEntity);
        return layoutUI;
    }

    private LayoutUI createSampleLayout() {
        LayoutUI sampleLayout = new LayoutUI();
        sampleLayout.setLoutName("SAMPLE_LAYOUT");

        List<LayoutItemUI> layoutItems = new ArrayList<>();

        // 루트 아이템 (항상 있어야 함)
        LayoutItemUI rootItem = new LayoutItemUI();
        rootItem.setLoutItemSerno(0);
        rootItem.setLoutItemDepth(0);
        rootItem.setLoutItemType("GROUP");
        rootItem.setLoutitempath("SAMPLE_LAYOUT");
        layoutItems.add(rootItem);

        // 샘플 필드
        LayoutItemUI sampleField = new LayoutItemUI();
        sampleField.setLoutItemSerno(1);
        sampleField.setLoutItemDepth(1);
        sampleField.setLoutItemName("sampleField");
        sampleField.setLoutItemDesc("샘플 필드입니다. 실제 레이아웃을 정의해주세요.");
        sampleField.setLoutItemType("FIELD");
        sampleField.setLoutItemDataType("STRING");
        sampleField.setLoutItemLength(100);
        sampleField.setLoutitempath("SAMPLE_LAYOUT.sampleField");
        layoutItems.add(sampleField);

        sampleLayout.setLayoutItems(layoutItems);
        return sampleLayout;
    }

    public String generateSpecTableFromLayout(LayoutUI layout) {
        if (layout == null || layout.getLayoutItems() == null || layout.getLayoutItems().isEmpty()) {
            layout = createSampleLayout();
        }

        // 기존 메서드 로직 그대로 사용
        StringBuilder html = new StringBuilder();
        html.append("<table class='api_table tb_col' cellspacing='0' cellpadding='0' width='100%'>")
                .append("<colgroup>\n")
                //.append("<col width='5%'>\n")   // No
                .append("<col width='8%'>\n")   // 구분 (Header/Body)
                .append("<col width='23%'>\n")  // 이름
                .append("<col width='29%'>\n")  // 설명
                .append("<col width='7%'>\n")   // 필수여부
                .append("<col width='10%'>\n")  // 항목유형
                .append("<col width='15%'>\n")  // 데이터유형
                .append("<col width='8%'>\n")   // 길이
                .append("</colgroup>\n")
                .append("<thead>\n")
                .append("<tr>\n")
                //.append("<th style='text-align: center;'>No</th>\n")
                .append("<th style='text-align: center;'>구분</th>\n")
                .append("<th style='text-align: center;'>이름</th>\n")
                .append("<th style='text-align: center;'>설명</th>\n")
                .append("<th style='text-align: center;'>필수여부</th>\n")
                .append("<th style='text-align: center;'>항목유형</th>\n")
                .append("<th style='text-align: center;'>데이터유형</th>\n")
                .append("<th style='text-align: center;'>길이</th>\n")
                .append("</tr>\n")
                .append("</thead>\n")
                .append("<tbody>\n");

        generateSpecTableRows(layout.getLayoutItems(), html);
        html.append("</tbody></table>");

        return html.toString();
    }

    private void generateSpecTableRows(List<LayoutItemUI> items, StringBuilder html) {
        if (items == null || items.isEmpty()) {
            return;
        }

        // serno=0인 root 항목 건너뛰기
        int startIndex = 0;
        while (startIndex < items.size() && items.get(startIndex).getLoutItemSerno() == 0) {
            startIndex++;
        }

        // HTTP 헤더 그룹 찾기
        Optional<LayoutItemUI> headerGroup = items.stream()
                .filter(item -> "http_header_part".equals(item.getLoutItemName()) && "GROUP".equalsIgnoreCase(item.getLoutItemType()))
                .findFirst();

        Set<String> headerItemPaths = new HashSet<>();
        if (headerGroup.isPresent()) {
            // 헤더 그룹의 모든 하위 항목의 경로를 저장
            String headerPath = headerGroup.get().getLoutitempath();
            headerItemPaths = items.stream()
                    .filter(item -> item.getLoutitempath() != null && item.getLoutitempath().startsWith(headerPath))
                    .map(LayoutItemUI::getLoutitempath)
                    .collect(Collectors.toSet());
        }

        int currentDepth = 1;
        Stack<Integer> depthStack = new Stack<>();
        depthStack.push(currentDepth);
        int seqNo = 1;

        for (int i = startIndex; i < items.size(); i++) {
            LayoutItemUI item = items.get(i);

            // http_header_part GROUP 항목은 테이블에서 제외
            if ("http_header_part".equals(item.getLoutItemName()) && "GROUP".equalsIgnoreCase(item.getLoutItemType())) {
                continue;
            }

            int itemDepth = item.getLoutItemDepth();

            // depth 변화에 따른 처리
            if (itemDepth > currentDepth) {
                depthStack.push(itemDepth);
            } else if (itemDepth < currentDepth) {
                while (!depthStack.isEmpty() && depthStack.peek() >= itemDepth) {
                    depthStack.pop();
                }
                depthStack.push(itemDepth);
            }
            currentDepth = itemDepth;

            // 해당 항목이 헤더에 속하는지 확인
            boolean isHeader = headerItemPaths.contains(item.getLoutitempath());
            String fieldName = item.getLoutItemName();
            if(!isHeader) {
                String indentation = getIndentation(depthStack.size() - 1);
                fieldName = indentation + fieldName;
            }

            String dataType = "FIELD".equalsIgnoreCase(item.getLoutItemType()) ?
                    mapDataType(item.getLoutItemDataType()) : "";

            html.append("<tr>")
                    //.append("<td style='text-align: center;'>").append(seqNo++).append("</td>")
                    .append("<td style='text-align: center;'>").append(isHeader ? "Header" : "Body").append("</td>")
                    // 두 번째 열(fieldName)에 대해 왼쪽 정렬과 공백 처리를 위한 스타일 적용
                    .append("<td style='text-align: left; padding-left: calc(2% + 4px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;'>")
                    .append(fieldName)
                    .append("</td>")
                    .append("<td style='text-align: left; padding-left: calc(2% + 4px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;'>")
                    .append(item.getLoutItemDesc())
                    .append("</td>")
                    .append("<td style='text-align: center;'></td>")
                    .append("<td style='text-align: center;'>").append(getItemTypeDisplay(item)).append("</td>")
                    .append("<td style='text-align: center;'>").append(dataType).append("</td>")
                    .append("<td style='text-align: right;'>").append(item.getLoutItemLength()).append("</td>")
                    .append("</tr>\n");
        }
    }

    public String generateSampleDataFromLayout(LayoutUI layout) {
        if (layout == null || layout.getLayoutItems() == null || layout.getLayoutItems().isEmpty()) {
            layout = createSampleLayout();
        }

        // 빈 root node 생성
        ObjectNode rootNode = objectMapper.createObjectNode();
        Map<String, ObjectNode> nodeMap = new HashMap<>();
        nodeMap.put(layout.getLoutName(), rootNode);  // root는 LoutName을 key로 저장

        // http_header_part GROUP을 찾아서 처리 제외
        Optional<LayoutItemUI> headerGroup = layout.getLayoutItems().stream()
                .filter(item -> "http_header_part".equals(item.getLoutItemName()) && "GROUP".equalsIgnoreCase(item.getLoutItemType()))
                .findFirst();

        Set<String> headerItemPaths = new HashSet<>();
        if (headerGroup.isPresent()) {
            String headerPath = headerGroup.get().getLoutitempath();
            headerItemPaths = layout.getLayoutItems().stream()
                    .filter(item -> item.getLoutitempath() != null && item.getLoutitempath().startsWith(headerPath))
                    .map(LayoutItemUI::getLoutitempath)
                    .collect(Collectors.toSet());
        }

        // List를 1번 인덱스부터 순차적으로 파싱
        List<LayoutItemUI> items = layout.getLayoutItems();
        for (int i = 1; i < items.size(); i++) {
            LayoutItemUI item = items.get(i);

            // 헤더 항목은 건너뛰기
            if (headerItemPaths.contains(item.getLoutitempath())) {
                continue;
            }

            String parentPath = getParentPath(item.getLoutitempath());
            ObjectNode parentNode = nodeMap.get(parentPath);

            switch (item.getLoutItemType().toUpperCase()) {
                case "FIELD":
                    addFieldValue(parentNode, item);
                    break;

                case "GROUP":
                    ObjectNode groupNode = parentNode.putObject(item.getLoutItemName());
                    nodeMap.put(item.getLoutitempath(), groupNode);
                    break;

                case "GRID":
                    ArrayNode arrayNode = parentNode.putArray(item.getLoutItemName());
                    ObjectNode arrayItem = arrayNode.addObject();
                    nodeMap.put(item.getLoutitempath(), arrayItem);
                    break;

                // JSON 레이아웃의 반복 항목(itemType="Array"). 자식이 있으면 객체배열, 없으면 스칼라배열.
                case "ARRAY":
                    ArrayNode arrNode = parentNode.putArray(item.getLoutItemName());
                    if (hasChildItems(items, item)) {
                        ObjectNode arrObj = arrNode.addObject();
                        nodeMap.put(item.getLoutitempath(), arrObj);
                    } else {
                        addScalarToArray(arrNode, item);
                    }
                    break;
            }
        }

        try {
            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
        } catch (Exception e) {
            throw new BizException("Failed to generate sample data", e);
        }
    }


    private String getItemTypeDisplay(LayoutItemUI item) {
        switch (item.getLoutItemType().toUpperCase()) {
            case "FIELD":
                return "Field";
            case "GROUP":
                return "Object";
            case "GRID":
                return "Array<Obj>";
            default:
                return item.getLoutItemType();
        }
    }

    private String mapDataType(String dataType) {
        if (dataType == null) return "string";  // JSON 스펙에 맞춰 소문자로
        switch (dataType.toUpperCase()) {
            case "INTEGER":
            case "INT":
            case "LONG":
            case "DECIMAL":
            case "DOUBLE":
            case "BIGDECIMAL":
                return "Number";    // 동일하게 number
            case "BOOLEAN":
                return "Bool";
            default:
                return "String";    // 기본값은 string
        }
    }


    private String getParentPath(String path) {
        int lastDotIndex = path.lastIndexOf('.');
        return lastDotIndex > 0 ? path.substring(0, lastDotIndex) : "";  // root의 경우 LoutName 반환
    }

    private void addFieldValue(ObjectNode node, LayoutItemUI item) {
        String dataType = item.getLoutItemDataType();
        String defaultValue = item.getLoutItemDefault();
        String fieldName = item.getLoutItemName();

        boolean isInteger = isIntegerType(dataType);

        if (isNumericType(dataType)) {
            if (StringUtils.isNotEmpty(defaultValue)) {
                try {
                    if (isInteger) {
                        node.put(fieldName, Integer.parseInt(defaultValue));
                    } else {
                        node.put(fieldName, Double.parseDouble(defaultValue));
                    }
                } catch (NumberFormatException e) {
                    node.put(fieldName, isInteger ? 0 : 0.0);
                }
            } else {
                node.put(fieldName, isInteger ? 0 : 0.0);
            }
        } else if ("BOOLEAN".equalsIgnoreCase(dataType)) {
            node.put(fieldName, StringUtils.isNotEmpty(defaultValue) ?
                    Boolean.parseBoolean(defaultValue) : false);
        } else {
            node.put(fieldName, StringUtils.isNotEmpty(defaultValue) ?
                    defaultValue : "sample_" + fieldName);
        }
    }

    /**
     * 지정 항목이 하위(자식) 항목을 가지는지 여부.
     * <p>JSON 레이아웃에서 배열/객체는 FIELD 노드(반복='*' → itemType="Array")로 저장되고
     * 자식 항목은 loutitempath 접두어 + depth+1 로 표현된다. 자식 유무로 객체배열/스칼라배열을 구분한다.
     */
    private boolean hasChildItems(List<LayoutItemUI> items, LayoutItemUI parent) {
        String parentPath = parent.getLoutitempath();
        if (parentPath == null) {
            return false;
        }
        String prefix = parentPath + ".";
        int childDepth = parent.getLoutItemDepth() + 1;
        for (LayoutItemUI it : items) {
            String p = it.getLoutitempath();
            if (p != null && p.startsWith(prefix) && it.getLoutItemDepth() == childDepth) {
                return true;
            }
        }
        return false;
    }

    /** 스칼라 배열(자식 없는 Array)의 예시 요소 1개를 배열에 추가한다. addFieldValue 의 배열 버전. */
    private void addScalarToArray(ArrayNode arrayNode, LayoutItemUI item) {
        String dataType = item.getLoutItemDataType();
        String defaultValue = item.getLoutItemDefault();
        boolean isInteger = isIntegerType(dataType);

        if (isNumericType(dataType)) {
            try {
                if (StringUtils.isNotEmpty(defaultValue)) {
                    if (isInteger) {
                        arrayNode.add(Integer.parseInt(defaultValue));
                    } else {
                        arrayNode.add(Double.parseDouble(defaultValue));
                    }
                } else {
                    arrayNode.add(isInteger ? 0 : 0.0);
                }
            } catch (NumberFormatException e) {
                arrayNode.add(isInteger ? 0 : 0.0);
            }
        } else if ("BOOLEAN".equalsIgnoreCase(dataType)) {
            arrayNode.add(StringUtils.isNotEmpty(defaultValue) ? Boolean.parseBoolean(defaultValue) : false);
        } else {
            arrayNode.add(StringUtils.isNotEmpty(defaultValue) ? defaultValue : "sample_" + item.getLoutItemName());
        }
    }

    private boolean isIntegerType(String dataType) {
        if (dataType == null) return false;
        switch (dataType.toUpperCase()) {
            case "INTEGER":
            case "INT":
            case "LONG":
                return true;
            default:
                return false;
        }
    }

    private boolean isNumericType(String dataType) {
        if (isIntegerType(dataType)) return true;
        if (dataType == null) return false;

        switch (dataType.toUpperCase()) {
            case "DECIMAL":
            case "DOUBLE":
            case "BIGDECIMAL":
                return true;
            default:
                return false;
        }
    }

    private String getIndentation(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("&nbsp;&nbsp;&nbsp;&nbsp;");
        }
        return sb.toString();
    }


    /**
     * Generates OpenAPI 3.0 specification for the given layout and API information
     */
    public String generateSwaggerSpec(LayoutUI requestLayout, LayoutUI responseLayout,
                                      Map<String, String> inboundAdapterSpec, ApiInterfaceUI apiInterface) {
        if (apiInterface == null) {
            throw new BizException("API 인터페이스 정보가 존재하지 않습니다.");
        }

        if (StringUtils.isEmpty(apiInterface.getInboundRestPath())) {
            throw new BizException("REST API 경로 정보가 존재하지 않습니다.");
        }

        if (StringUtils.isEmpty(apiInterface.getInboundHttpMethod())) {
            throw new BizException("HTTP 메소드 정보가 존재하지 않습니다.");
        }

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode swaggerSpec = objectMapper.createObjectNode();

            // OpenAPI version
            swaggerSpec.put("openapi", "3.0.0");

            // Info section
            ObjectNode info = swaggerSpec.putObject("info");
            info.put("title", StringUtils.defaultString(apiInterface.getEaiSvcName(), "API Specification"));
            info.put("version", "1.0.0");

            // Servers section
            ArrayNode servers = swaggerSpec.putArray("servers");
            ObjectNode server = servers.addObject();
            String baseUrl = StringUtils.defaultString(inboundAdapterSpec.get("urlPath"), "/");
            server.put("url", baseUrl);

            // Paths section
            ObjectNode paths = swaggerSpec.putObject("paths");
            String pathUrl = apiInterface.getInboundRestPath();
            if (!pathUrl.startsWith("/")) {
                pathUrl = "/" + pathUrl;
            }

            ObjectNode pathItem = paths.putObject(pathUrl);
            String httpMethod = apiInterface.getInboundHttpMethod().toLowerCase();
            ObjectNode operation = pathItem.putObject(httpMethod);

            // Operation details
            operation.put("summary", StringUtils.defaultString(apiInterface.getEaiSvcName(), "API Operation"));
            ArrayNode tags = operation.putArray("tags");
            tags.add("KBANK API");

            // Request 처리
            if (requestLayout != null && requestLayout.getLayoutItems() != null) {
                // 헤더/바디 분리
                RequestComponents components = separateHeaderAndBody(requestLayout.getLayoutItems());

                boolean bodyRequired = isRequestBodyRequired(httpMethod);

                // 헤더 파라미터 + (POST/PUT 이 아니면) 요청 바디 항목을 쿼리스트링 파라미터로 함께 담는다.
                boolean hasHeader = !components.headerItems.isEmpty();
                boolean queryFromBody = !bodyRequired && !components.bodyItems.isEmpty();
                if (hasHeader || queryFromBody) {
                    ArrayNode parameters = operation.putArray("parameters");
                    if (hasHeader) {
                        generateHeaderParameters(parameters, components.headerItems);
                    }
                    // GET/DELETE 등 바디 미사용 메소드: 요청 레이아웃 바디 항목 → 쿼리 파라미터(in=query)
                    if (queryFromBody) {
                        generateQueryParameters(parameters, components.bodyItems);
                    }
                }

                // Body 처리 (POST, PUT 메소드인 경우)
                if (bodyRequired) {
                    ObjectNode requestBody = operation.putObject("requestBody");
                    requestBody.put("required", true);
                    ObjectNode content = requestBody.putObject("content");
                    ObjectNode mediaType = content.putObject(getContentType(apiInterface));
                    ObjectNode schema = mediaType.putObject("schema");
                    generateSchemaFromLayoutItems(schema, components.bodyItems);
                }
            }

            // Response 처리
            ObjectNode responses = operation.putObject("responses");
            ObjectNode successResponse = responses.putObject("200");
            successResponse.put("description", "Successful response");

            if (responseLayout != null && responseLayout.getLayoutItems() != null) {
                // 응답 헤더/바디 분리
                RequestComponents responseComponents = separateHeaderAndBody(responseLayout.getLayoutItems());

                // 응답 헤더 처리
                if (!responseComponents.headerItems.isEmpty()) {
                    ObjectNode headers = successResponse.putObject("headers");
                    generateResponseHeaders(headers, responseComponents.headerItems);
                }

                // 응답 바디 처리
                ObjectNode responseContent = successResponse.putObject("content");
                ObjectNode responseMediaType = responseContent.putObject(getContentType(apiInterface));
                ObjectNode responseSchema = responseMediaType.putObject("schema");
                generateSchemaFromLayoutItems(responseSchema, responseComponents.bodyItems);
            } else {
                // 응답 레이아웃이 없는 경우 기본 응답 스키마
                ObjectNode responseContent = successResponse.putObject("content");
                ObjectNode responseMediaType = responseContent.putObject(getContentType(apiInterface));
                ObjectNode responseSchema = responseMediaType.putObject("schema");
                responseSchema.put("type", "object");
                responseSchema.put("description", "응답 레이아웃이 정의되지 않았습니다.");
            }

            return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(swaggerSpec);
        } catch (Exception e) {
            throw new BizException("Swagger 스펙 생성 중 오류가 발생했습니다: " + e.getMessage(), e);
        }
    }

    private static class RequestComponents {
        List<LayoutItemUI> headerItems = new ArrayList<>();
        List<LayoutItemUI> bodyItems = new ArrayList<>();
    }

    private RequestComponents separateHeaderAndBody(List<LayoutItemUI> items) {
        RequestComponents components = new RequestComponents();
        if (items == null || items.isEmpty()) {
            return components;
        }

        // HTTP 헤더 그룹 찾기
        Optional<LayoutItemUI> headerGroup = items.stream()
                .filter(item -> "http_header_part".equals(item.getLoutItemName()) && "GROUP".equalsIgnoreCase(item.getLoutItemType()))
                .findFirst();

        Set<String> headerItemPaths = new HashSet<>();
        if (headerGroup.isPresent()) {
            String headerPath = headerGroup.get().getLoutitempath();
            headerItemPaths = items.stream()
                    .filter(item -> item.getLoutitempath() != null && item.getLoutitempath().startsWith(headerPath))
                    .map(LayoutItemUI::getLoutitempath)
                    .collect(Collectors.toSet());
        }

        // 아이템 분류
        for (LayoutItemUI item : items) {
            // http_header_part GROUP 항목 자체는 제외
            if ("http_header_part".equals(item.getLoutItemName()) && "GROUP".equalsIgnoreCase(item.getLoutItemType())) {
                continue;
            }

            if (headerItemPaths.contains(item.getLoutitempath())) {
                components.headerItems.add(item);
            } else {
                components.bodyItems.add(item);
            }
        }

        return components;
    }

    private void generateHeaderParameters(ArrayNode parameters, List<LayoutItemUI> headerItems) {
        for (LayoutItemUI item : headerItems) {
            if ("FIELD".equalsIgnoreCase(item.getLoutItemType())) {
                ObjectNode parameter = parameters.addObject();
                parameter.put("name", item.getLoutItemName());
                parameter.put("in", "header");
                parameter.put("description", item.getLoutItemDesc());
                parameter.put("required", false); // 기본값으로 false 설정, 필요시 true로 변경 가능

                ObjectNode schema = parameter.putObject("schema");
                schema.put("type", mapSwaggerType(item.getLoutItemDataType()));
                int length = item.getLoutItemLength();
                if (length > 0) {
                    schema.put("maxLength", item.getLoutItemLength());
                }
            }
        }
    }

    /**
     * GET/DELETE 등 바디 미사용 메소드: 요청 레이아웃 바디 항목을 쿼리스트링 파라미터(in=query)로 매핑.
     * 스칼라 FIELD 만 대상(GROUP/GRID 중첩 객체·배열은 쿼리스트링에 부적합하여 제외), root(serno 0)는 스킵.
     */
    private void generateQueryParameters(ArrayNode parameters, List<LayoutItemUI> bodyItems) {
        for (LayoutItemUI item : bodyItems) {
            if (item.getLoutItemSerno() != null && item.getLoutItemSerno() == 0) {
                continue;
            }
            if (!"FIELD".equalsIgnoreCase(item.getLoutItemType())) {
                continue;
            }
            ObjectNode parameter = parameters.addObject();
            parameter.put("name", item.getLoutItemName());
            parameter.put("in", "query");
            if (StringUtils.isNotEmpty(item.getLoutItemDesc())) {
                parameter.put("description", item.getLoutItemDesc());
            }
            parameter.put("required", false); // 기본값 false, 필요시 사용자가 변경

            ObjectNode schema = parameter.putObject("schema");
            schema.put("type", mapSwaggerType(item.getLoutItemDataType()));
            int length = item.getLoutItemLength();
            if (length > 0) {
                schema.put("maxLength", length);
            }
        }
    }

    private void generateResponseHeaders(ObjectNode headers, List<LayoutItemUI> headerItems) {
        for (LayoutItemUI item : headerItems) {
            if ("FIELD".equalsIgnoreCase(item.getLoutItemType())) {
                ObjectNode header = headers.putObject(item.getLoutItemName());
                header.put("description", item.getLoutItemDesc());
                ObjectNode schema = header.putObject("schema");
                schema.put("type", mapSwaggerType(item.getLoutItemDataType()));
                int length = item.getLoutItemLength();
                if (length > 0) {
                    schema.put("maxLength", item.getLoutItemLength());
                }
            }
        }
    }

    private void generateSchemaFromLayoutItems(ObjectNode schemaNode, List<LayoutItemUI> layoutItems) {
        if (layoutItems == null || layoutItems.isEmpty()) {
            schemaNode.put("type", "object");
            return;
        }

        schemaNode.put("type", "object");
        ObjectNode properties = schemaNode.putObject("properties");

        Map<Integer, ObjectNode> depthMap = new HashMap<>();
        depthMap.put(0, properties);

        for (LayoutItemUI item : layoutItems) {
            if (item.getLoutItemSerno() == 0) continue;

            ObjectNode parentNode = depthMap.get(item.getLoutItemDepth() - 1);
            if (parentNode == null) continue;

            switch (item.getLoutItemType().toUpperCase()) {
                case "FIELD":
                    ObjectNode fieldSchema = parentNode.putObject(item.getLoutItemName());
                    fieldSchema.put("type", mapSwaggerType(item.getLoutItemDataType()));
                    if (StringUtils.isNotEmpty(item.getLoutItemDesc())) {
                        fieldSchema.put("description", item.getLoutItemDesc());
                    }
                    int length = item.getLoutItemLength();
                    if (length > 0) {
                        fieldSchema.put("maxLength", item.getLoutItemLength());
                    }
                    break;

                case "GROUP":
                    ObjectNode groupSchema = parentNode.putObject(item.getLoutItemName());
                    groupSchema.put("type", "object");
                    ObjectNode groupProps = groupSchema.putObject("properties");
                    depthMap.put(item.getLoutItemDepth(), groupProps);
                    break;

                case "GRID":
                    ObjectNode arraySchema = parentNode.putObject(item.getLoutItemName());
                    arraySchema.put("type", "array");
                    ObjectNode arrayItemSchema = arraySchema.putObject("items");
                    arrayItemSchema.put("type", "object");
                    ObjectNode arrayItemProps = arrayItemSchema.putObject("properties");
                    depthMap.put(item.getLoutItemDepth(), arrayItemProps);
                    break;

                // JSON 레이아웃의 반복 항목(itemType="Array"). 자식이 있으면 객체배열(items.properties), 없으면 스칼라배열.
                case "ARRAY":
                    ObjectNode arrSchema = parentNode.putObject(item.getLoutItemName());
                    arrSchema.put("type", "array");
                    ObjectNode arrItems = arrSchema.putObject("items");
                    if (hasChildItems(layoutItems, item)) {
                        arrItems.put("type", "object");
                        depthMap.put(item.getLoutItemDepth(), arrItems.putObject("properties"));
                    } else {
                        arrItems.put("type", mapSwaggerType(item.getLoutItemDataType()));
                    }
                    break;
            }
        }
    }

    private String mapSwaggerType(String dataType) {
        if (dataType == null) return "string";

        switch (dataType.toUpperCase()) {
            case "INTEGER":
            case "INT":
                return "integer";
            case "LONG":
                return "integer";
            case "DECIMAL":
            case "DOUBLE":
            case "BIGDECIMAL":
                return "number";
            case "BOOLEAN":
                return "boolean";
            default:
                return "string";
        }
    }

    private boolean isRequestBodyRequired(String method) {
        if (method == null) return false;
        return "POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method);
    }

    private String getContentType(ApiInterfaceUI apiInterface) {
        String contentType = apiInterface.getRestContentType();
        return StringUtils.defaultIfEmpty(contentType, "application/json");
    }
}