package incheon.uis.ums.util;


import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import incheon.uis.ums.service.UisAccessService;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class UisConfigUtils {

    private final ResourceLoader resourceLoader;
    private final UisAccessService uisAccessService;
    // 스프링 의존성 주입용 생성자
    @Autowired
    public UisConfigUtils(ResourceLoader resourceLoader, UisAccessService uisAccessService) {
        this.resourceLoader = resourceLoader;
        this.uisAccessService = uisAccessService;
    }

    /**
     * yml 설정에서 geometry 타입의 필드명을 찾는 메서드
     * @param ymlData 파싱된 yml 데이터
     * @return geometry 타입 필드명, 없으면 null
     */
    public String findGeometryFieldName(Map<String, Object> ymlData) {
        try {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> fieldList = (List<Map<String, Object>>) ymlData.get("field-list");

            if (fieldList != null) {
                for (Map<String, Object> field : fieldList) {
                    String type = (String) field.get("type");
                    if ("geometry".equals(type)) {
                        return (String) field.get("id");
                    }
                }
            }
        } catch (RuntimeException e) {
            log.debug("Geometry 필드 검색 중 오류", e);
        }
        return null;
    }

    /**
     * yml 설정에서 특정 type의 필드명을 찾는 메서드
     * @param ymlData 파싱된 yml 데이터
     * @param fieldType 찾을 필드 타입
     * @return 해당 타입의 필드명, 없으면 null
     */
    public String findFieldNameByType(Map<String, Object> ymlData, String fieldType) {
        try {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> fieldList = (List<Map<String, Object>>) ymlData.get("field-list");

            if (fieldList != null) {
                for (Map<String, Object> field : fieldList) {
                    String type = (String) field.get("type");
                    if (fieldType.equals(type)) {
                        return (String) field.get("id");
                    }
                }
            }
        } catch (RuntimeException e) {
            log.debug("{} 타입 필드 검색 중 오류", fieldType, e);
        }
        return null;
    }
    
    /**
     * yml 파일에서 대장검색/시설물검색 정보를 파싱하는 메서드
     * @return Parshing 정보 (모든 대장/시설물 종류와 검색필드 포함)
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> parseSearchYmlData(String fileName) throws Exception {

        ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
        Map<String, Object> yamlData;

        InputStream inputStream = null;
        try {
            Resource resource = resourceLoader.getResource("classpath:uisMap/" + fileName + ".yml");

            if (!resource.exists()) {
                // 빈 데이터 반환 or 호출부에서 처리 가능하게
                return new HashMap<>();
            }

            inputStream = resource.getInputStream();
            yamlData = mapper.readValue(inputStream, Map.class);

        } catch (IOException e) {
            log.error("YAML 파일 읽기/파싱 실패: fileName={}", fileName, e);
            throw new IllegalStateException("설정 파일을 읽을 수 없습니다.");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException closeEx) {
                    log.error("YAML InputStream close 실패 (무시): fileName={}", fileName, closeEx);
                }
            }
        }

        /**
         * 배열에서 모든 필드 정보 추출
         * 예시 :
         *  {
                "MNGT": {
                    "name": "공사기관",
                    "type": "combo",
                    "options": [...],
                    "code": "278"
                },
                "IDN": {
                    "name": "공사번호",
                    "type": "txt"
                },
                "SYMD": {
                    "name": "계약상년월일",
                    "type": "date"
                }
                // ...
            }
         */
        
        // 필드 정보 추출 및 매핑 정보 생성
        Map<String, Object> allFieldInfo = this.getFieldInfo((List<Map<String, Object>>) yamlData.get("field-list"));
        
        // 배열에서 모든 속성 정보 추출
        List<Map<String, Object>> list = (List<Map<String, Object>>) yamlData.get(fileName);
        
        Map<String, Object> data = new HashMap<>();
        List<Map<String, Object>> types = new ArrayList<>();
        
        // 각 대장/시설물 종류별로 정보 파싱
        for (Map<String, Object> targetMap : list) {
            Map<String, Object> info = new HashMap<>();
            
            //2025-11-06 seyoung 사용자 권한 체크 로직
            final String typeId = (String)targetMap.get("id") != null ? (String)targetMap.get("id") : "";
            //권한 맞지 않으면 해당 target 파싱 진행 X
            if(!uisAccessService.hasAuthrtAccessByTypeId(typeId)) continue;

            // 기본 정보
            info.put("id", targetMap.get("id"));
            info.put("name", targetMap.get("name"));
            info.put("description", targetMap.get("description"));
            info.put("original", targetMap.get("original"));
            
            // 탭 정보 파싱
            List<Map<String, Object>> tabs = (List<Map<String, Object>>) targetMap.get("tabs");
            List<Map<String, Object>> parsedTabs = new ArrayList<>();
            
            for (Map<String, Object> tab : tabs) {
            	
            	// tab 내 배치된 필드리스트가 우선
                Map<String, Object> fieldInfo = this.getFieldInfo((List<Map<String, Object>>) tab.get("field-list"));
                List<Map<String, Object>> searchFields = (List<Map<String, Object>>) tab.get("search-fields");
                
                // searchFields 가 없으면 제외
                if(searchFields == null || searchFields.isEmpty()) continue;
                
                Map<String, Object> tabInfo = new HashMap<>();
                tabInfo.put("id", tab.get("id"));
                tabInfo.put("tab-name", tab.get("tab-name"));
                
                List<Map<String, Object>> parsedSearchFields = searchFields.stream()
                    .map(field -> {
                        String fieldId = (String) field.get("id");
                        Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
                        if (baseInfo == null) {
                        	log.debug("[" + info + "] search-fields not match to field-list : " + fieldId);
                        	return null;
                        }
                        
                        Map<String, Object> searchField = new HashMap<>();
                        searchField.put("id", fieldId);
                        
                        //name만 오버라이드하는 경우
                        String overrideName = (String) field.get("name");
                        
                        if (overrideName != null && !overrideName.isEmpty()) {
                            searchField.put("name", overrideName);
                        } else {
                            searchField.put("name", baseInfo.get("name"));
                        }
                        
                        // type 처리: combo인 경우와 아닌 경우
                        String baseType = (String) baseInfo.get("type");
                        String conditions = (String) field.get("conditions");
                        String defaultValue = (String) field.get("default");
                        if(defaultValue != null && !defaultValue.isEmpty()) {
                            searchField.put("default", defaultValue);
                        }
                        if ("combo".equals(baseType)) {
                            searchField.put("type", "combo");
                            if (baseInfo.get("options") != null) {
                                searchField.put("options", baseInfo.get("options"));
                                searchField.put("code", baseInfo.get("code"));
                            }//2025-10-23 seyoung : combo option 하드코딩(search-field )
                            else if(baseInfo.get("values")!= null) {
                            	List<Map<String, Object>> valueList = (List<Map<String, Object>>) baseInfo.get("values");
                                searchField.put("values", valueList);
                            }
						} else if ("txt".equals(baseType) || "num".equals(baseType)) { // 2025-10-23 호지원 length 추가
							if(baseInfo.get("length") != null) {
								searchField.put("length", baseInfo.get("length"));
							}
								searchField.put("type", baseType + "-" + conditions);
						} else { 
							searchField.put("type", baseType + "-" + conditions); 
						}

                        // attributes 정보 복사(required 등)
                        List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
                        if (attributes != null) {
                            searchField.put("attributes", attributes);
                        }

                        return searchField;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
                
                // 검색결과 필드 파싱
                List<Map<String, Object>> searchResult = (List<Map<String, Object>>) tab.get("search-result");
                List<Map<String, Object>> parsedSearchResult = searchResult.stream()
                    .map(field -> {
                        String fieldId = (String) field.get("id");
                        Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
                        if (baseInfo == null) {
                        	log.debug("[" + info + "] search-result not match to field-list : " + fieldId);
                        	return null;
                        }

                        Map<String, Object> resultField = new HashMap<>();
                        resultField.put("id", fieldId);
                        
                        //name만 오버라이드하는 경우
                        String overrideName = (String) field.get("name");
                        
                        if (overrideName != null && !overrideName.isEmpty()) {
                            resultField.put("name", overrideName);
                        } else {
                            resultField.put("name", baseInfo.get("name"));
                        }

                        //minWidth(검색결과 테이블 스타일 설정 (ex. "300px"))
                        String minWidth = (String) field.get("minWidth");
                        if (minWidth != null && !minWidth.isEmpty()) {
                            resultField.put("minWidth", minWidth);
                        }

                        resultField.put("type", baseInfo.get("type"));  // 원본 type 그대로 사용
                        // combo의 경우 code(list_id)도 함께 전달
                        if (baseInfo.get("code") != null) {
                            resultField.put("code", baseInfo.get("code"));
                        }else if(baseInfo.get("values") != null) {
                        	//2025-10-23 seyoung: combo option 하드코딩(searchResult)
                            resultField.put("values", baseInfo.get("values"));
                        }

                        // attributes 정보 복사 (hidden 등)
                        List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
                        if (attributes != null) {
                            resultField.put("attributes", attributes);
                        }

                        return resultField;
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
                
                if(parsedSearchFields.isEmpty() || parsedSearchResult.isEmpty()) {
                	log.debug("parsedSearchFields.isEmpty() || parsedSearchResult.isEmpty() : " + tab.get("id"));
                	continue;
                }
                tabInfo.put("search-fields", parsedSearchFields);
                tabInfo.put("search-result", parsedSearchResult);
                
                parsedTabs.add(tabInfo);
            }
            
            // 2025-10-21 호지원 search-field 없는 경우 목록에서 제외
            if (parsedTabs != null && !parsedTabs.isEmpty()) {
            	info.put("tabs", parsedTabs);
                types.add(info);
            }

        }
        
        data.put("types", types);
        
        return data;
    }
    
    /**
     * yml 파일에서 대장검색/시설물검색 정보를 파싱하는 메서드
     * @return Parshing 정보 (모든 대장/시설물 종류와 검색필드 포함)
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> parseDetailYmlData(String typeId) throws Exception {
    	
    	String[] ymlFileNames = { "construction", "facilities" };
    	Map<String, Object> data = new HashMap<>();
        List<Map<String, Object>> types = new ArrayList<>();
        String typeGb = ymlFileNames[0];
        
        if(StringUtils.isBlank(typeId)) {
        	log.error("[UisConfigUtil] parseDetailYmlData Parameter typeId is null!");
        	return data;
        }
    	for(String fileName : ymlFileNames)
    	{
            // YAML 파일 읽기
            InputStream inputStream = null;

            Map<String, Object> yamlData = null;
            // YAML 파서 설정
            ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
            try {
                if (typeId == null) {
                    continue;
                }

                // 시설물은 프리픽스 제외 (원본 typeId를 건드리지 않도록 로컬 변수 사용)
                String searchTypeId = typeId;
                if ("facilities".equals(fileName) && searchTypeId.contains(":")) {
                    searchTypeId = searchTypeId.substring(searchTypeId.indexOf(':') + 1);
                }

                // YAML 파일 읽기 (필요할 때만 오픈)
                Resource resource = resourceLoader.getResource("classpath:uisMap/" + fileName + ".yml");
                if (!resource.exists()) {
                    continue;
                }
                inputStream = resource.getInputStream();
                // YAML을 Map으로 파싱
                yamlData = mapper.readValue(inputStream, Map.class);

            } catch (IOException e) {
                log.error("YAML 파일 읽기/파싱 실패: fileName={}", fileName, e);
                throw new IllegalStateException("설정 파일을 읽을 수 없습니다.");
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException ignore) {
                        log.error("YAML InputStream close 실패 (무시): fileName={}", fileName, ignore);
                    }
                }
            }

            /**
             * 배열에서 모든 필드 정보 추출
             * 예시 :
             *  {
                    "MNGT": {
                        "name": "공사기관",
                        "type": "combo",
                        "options": [...],
                        "code": "278"
                    },
                    "IDN": {
                        "name": "공사번호",
                        "type": "txt"
                    },
                    "SYMD": {
                        "name": "계약상년월일",
                        "type": "date"
                    }
                    // ...
                }
             */
	        
	        // 필드 정보 추출 및 매핑 정보 생성
	        Map<String, Object> allFieldInfo = this.getFieldInfo((List<Map<String, Object>>) yamlData.get("field-list"));
	
	        // 배열에서 모든 속성 정보 추출
	        List<Map<String, Object>> list = (List<Map<String, Object>>) yamlData.get(fileName);
	        
	        final String filterTypeId = typeId;
	        Optional <Map<String, Object>> targetMap = list.stream()
	        	    .filter(yml -> filterTypeId.equals(yml.get("id")))
	        	    .findFirst();
	        
	        if (targetMap.isPresent()) {
	            Map<String, Object> typeMap = targetMap.get();

	            // tabs 하위에 search-detail이 존재하는 경우에만 처리
	            List<Map<String, Object>> tabs = (List<Map<String, Object>>) typeMap.get("tabs");
	            if (tabs == null) continue;

	            boolean hasSearchDetail = tabs.stream()
	                .anyMatch(tab -> tab.containsKey("search-detail") && tab.get("search-detail") != null);

	            if (!hasSearchDetail) continue;

		        // 각 대장/시설물 종류별로 정보 파싱
	            Map<String, Object> info = new HashMap<>();
	            
	            // 기본 정보
	            info.put("id", typeMap.get("id"));
	            info.put("name", typeMap.get("name"));
	            info.put("description", typeMap.get("description"));
	            
	            // 탭 정보 파싱
	            List<Map<String, Object>> parsedTabs = new ArrayList<>();
	            
	            for (Map<String, Object> tab : tabs) {
	                Map<String, Object> tabInfo = new HashMap<>();
	                tabInfo.put("id", tab.get("id"));
	                tabInfo.put("tab-name", tab.get("tab-name"));
	                
	                // tab 내 배치된 필드리스트가 우선
	                Map<String, Object> fieldInfo = this.getFieldInfo((List<Map<String, Object>>) tab.get("field-list"));

	                // 상세정보 필드 파싱 (search-detail)
	                List<Map<String, Object>> searchDetail = (List<Map<String, Object>>) tab.get("search-detail");
	                if (searchDetail != null) {
	                	typeGb = fileName;
	                    List<Map<String, Object>> parsedSearchDetail = searchDetail.stream()
	                        .map(field -> {
	                            String fieldId = (String) field.get("id");
	                            Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
		                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
	                            
	                            Map<String, Object> detailField = new HashMap<>();
	                            detailField.put("id", fieldId);
	                            
	                            // baseInfo가 있으면 name과 type 설정, 없으면 fieldId를 name으로 사용
	                            if (baseInfo != null) {
	                                
	                                //name만 오버라이드할 경우
	                                String overrideName = (String) field.get("name");

	                                if (overrideName != null && !overrideName.isEmpty()) {
	                                    detailField.put("name", overrideName);
	                                } else {
	                                    detailField.put("name", baseInfo.get("name"));
	                                }
	                                
	                                detailField.put("type", baseInfo.get("type"));
	                                // combo의 경우 code(list_id)도 함께 전달
	                                if (baseInfo.get("code") != null) {
	                                    detailField.put("code", baseInfo.get("code"));
	                                // 2025-10-23 seyoung :  combo option 하드코딩(searchDetail)
	                                }else if(baseInfo.get("values") != null) {
	                                    detailField.put("values", baseInfo.get("values"));
	                                }
        							
	                                // 2025-10-27 호지원 length 추가
	                                if(baseInfo.get("length") != null) {
	                                	detailField.put("length", baseInfo.get("length"));
        							}
	                                
	                            } else {
	                                detailField.put("name", fieldId);
	                                detailField.put("type", "txt");
	                            }
	
	                            // search-detail에 직접 정의된 type이 있으면 덮어쓴다.
	                            if (field.get("type") != null) {
	                                detailField.put("type", field.get("type"));
	                            }
	
	                            // default 가 있는 경우 함께 전달
	                            if (field.get("default") != null) {
	                                detailField.put("default", field.get("default"));
	                            }
	                            
	                            // attributes 정보 그대로 복사
	                            List<Map<String, Object>> attributes = (List<Map<String, Object>>) field.get("attributes");
	                            if (attributes != null) {
	                                detailField.put("attributes", attributes);
	                            }
	                            
	                            return detailField;
	                        })
	                        .collect(Collectors.toList());
	                    tabInfo.put("search-detail", parsedSearchDetail);
	                } else {
	                    // search-detail이 없으면 빈 배열로 설정
	                    tabInfo.put("search-detail", new ArrayList<>());
	                }
	
	                // 상세 하위 탭(detail-tab) 파싱
	                List<Map<String, Object>> detailTabs = (List<Map<String, Object>>) tab.get("detail-tab");
	                if (detailTabs != null) {
	                    List<Map<String, Object>> parsedDetailTabs = detailTabs.stream()
	                        .map(dtab -> {
	                            Map<String, Object> dti = new HashMap<>();
	                            dti.put("id", dtab.get("id"));
	                            dti.put("name", dtab.get("name"));

								List<Map<String, Object>> buttons = (List<Map<String, Object>>) dtab.get("buttons");
								if (buttons != null) {
									List<Map<String, Object>> parsedDtbuttons = buttons.stream()
	                                    .map(button -> {
	                                        Map<String, Object> b = new HashMap<>();
	                                        b.put("id", button.get("id"));
											b.put("textContent", button.get("textContent"));
	                                        return b;
	                                    })
	                                    .collect(Collectors.toList());
									dti.put("buttons", parsedDtbuttons);
								}
	
	                            // 탭 검색 키(search-fields) 파싱: id 및 param 매핑 보존
	                            List<Map<String, Object>> dtSearchFields = (List<Map<String, Object>>) dtab.get("search-fields");
	                            if (dtSearchFields != null) {
	                                List<Map<String, Object>> parsedDtSearchFields = dtSearchFields.stream()
	                                    .map(sf -> {
	                                        Map<String, Object> f = new HashMap<>();
	                                        f.put("id", sf.get("id"));
	                                        if (sf.get("param") != null) f.put("param", sf.get("param"));
	                                        return f;
	                                    })
	                                    .collect(Collectors.toList());
	                                dti.put("search-fields", parsedDtSearchFields);
	                            } else {
	                                dti.put("search-fields", new ArrayList<>());
	                            }
	
	                            // 탭 리스트 필드(list-fields) 파싱: id, type 및 attributes 보존
	                            List<Map<String, Object>> dtListFields = (List<Map<String, Object>>) dtab.get("list-fields");
	                            if (dtListFields != null) {
	                                List<Map<String, Object>> parsedDtListFields = dtListFields.stream()
	                                    .map(lf -> {
	                                        String fieldId = (String) lf.get("id");
	                                        Map<String, Object> baseInfo = (Map<String, Object>) fieldInfo.get(fieldId);
	            	                        if (baseInfo == null) baseInfo = (Map<String, Object>) allFieldInfo.get(fieldId);
	            	                        
	                                        Map<String, Object> out = new HashMap<>();
	                                        out.put("id", fieldId);
	                                        if (baseInfo != null && baseInfo.get("name") != null) out.put("name", baseInfo.get("name"));
	                                        if (baseInfo != null && baseInfo.get("type") != null) out.put("type", baseInfo.get("type"));
	                                        if (baseInfo != null && baseInfo.get("code") != null) out.put("code", baseInfo.get("code"));
	                                        //detail-tab name 오버라이드
	                                        String fieldNm = (String) lf.get("name");
	                                        if(fieldNm != null) {
	                                        	out.put("name", fieldNm);
	                                        }
	                                        //2025-10-23 seyoung: combo option 하드코딩(detail-tab)
	                                        if (baseInfo != null && baseInfo.get("values") != null) out.put("values", baseInfo.get("values"));
	                                        List<Map<String, Object>> attrs = (List<Map<String, Object>>) lf.get("attributes");
	                                        if (attrs != null) out.put("attributes", attrs);
	                                        //2025-12-03 seyoung: detail-tab minWidth 파싱
	                                        String minWidth = (String) lf.get("minWidth");
	                                        if (minWidth != null) out.put("minWidth", minWidth);
	                                        return out;
	                                    })
	                                    .collect(Collectors.toList());
	                                dti.put("list-fields", parsedDtListFields);
	                            } else {
	                                dti.put("list-fields", new ArrayList<>());
	                            }
	
	                            // 2025-11-28 seyoung : 연결 팝업 설정
	                            List<Map<String, Object>> relInfoList = (List<Map<String, Object>>) dtab.get("rel-info");

	                            if (relInfoList != null && !relInfoList.isEmpty()) {

	                                Map<String, Object> relInfo = new HashMap<>();

	                                // 공통: source / relation / target 엔티티
	                                String sourceEntity   = null;
	                                String relationEntity = null; // N:M 에서만 존재
	                                String targetEntity   = null;
	                                String updateTarget   = null;

	                                List<Map<String, Object>> relationDetailFields = new ArrayList<>();  // detail-fields 원본
	                                List<Map<String, Object>> targetSearchFields   = new ArrayList<>();  // search-fields 원본
	                                List<Map<String, Object>> targetListFields     = new ArrayList<>();  // list-fields 원본

	                                List<Map<String, Object>> keyMappings = new ArrayList<>();

	                                for (Map<String, Object> item : relInfoList) {

	                                    // 1. source 엔티티 (main이 되는 테이블)
	                                    if (item.containsKey("source")) {
	                                        sourceEntity = (String) item.get("source");
	                                    }

	                                    // 2. relation 엔티티 (N:M 의 중간 연결 테이블)
	                                    if (item.containsKey("relation")) {
	                                        relationEntity = (String) item.get("relation");

	                                        // relation 의 detail-fields
	                                        Object dfObj = item.get("detail-fields");
	                                        if (dfObj instanceof List) {
	                                            List<Map<String, Object>> dfList = (List<Map<String, Object>>) dfObj;
	                                            relationDetailFields.addAll(dfList);
	                                        }
	                                    }

	                                    // 3. target 엔티티 (연결할 테이블)
	                                    if (item.containsKey("target")) {
	                                        targetEntity = (String) item.get("target");

	                                        // target 검색 조건 search-fields
	                                        Object sfObj = item.get("search-fields");
	                                        if (sfObj instanceof List) {
	                                            List<Map<String, Object>> sfList = (List<Map<String, Object>>) sfObj;
	                                            targetSearchFields.addAll(sfList);
	                                        }

	                                        // target 리스트 컬럼 list-fields
	                                        Object lfObj = item.get("list-fields");
	                                        if (lfObj instanceof List) {
	                                            List<Map<String, Object>> lfList = (List<Map<String, Object>>) lfObj;
	                                            targetListFields.addAll(lfList);
	                                        }

	                                        // key-mapping (source/target/relation)
	                                        Object kmObj = item.get("key-mapping");
	                                        if (kmObj instanceof List) {
	                                            List<Map<String, Object>> kmList = (List<Map<String, Object>>) kmObj;
	                                            for (Map<String, Object> km : kmList) {
	                                                Map<String, Object> m = new HashMap<>();
	                                                if (km.containsKey("source")) {
	                                                    m.put("source", km.get("source"));
	                                                }
	                                                if (km.containsKey("target")) {
	                                                    m.put("target", km.get("target"));
	                                                }
	                                                if (km.containsKey("relation")) {
	                                                    m.put("relation", km.get("relation"));
	                                                }
	                                                if (km.containsKey("default")) {
	                                                    m.put("default", km.get("default"));
	                                                }
	                                                keyMappings.add(m);
	                                            }
	                                        }
	                                    }

	                                    // 4. update target 설정
	                                    if(item.containsKey("update-target")) {
	                                        updateTarget = (String) item.get("update-target");
	                                    }
	                                    
	                                }

	                                // loop 종료 후 최종 판단
	                                if (updateTarget == null) {
	                                    if (relationEntity != null) {
	                                        updateTarget = "relation";
	                                    } else {
	                                        updateTarget = "target";
	                                    }
	                                }

	                                relInfo.put("source-entity",   sourceEntity);
	                                relInfo.put("relation-entity", relationEntity);   // N:M 아닐 때는 null 가능
	                                relInfo.put("target-entity",   targetEntity);
	                                relInfo.put("update-target",   updateTarget);

	                                relInfo.put("relation-detail-fields", relationDetailFields); // detail-fields 전체
	                                relInfo.put("target-search-fields",   targetSearchFields);   // search-fields 전체
	                                relInfo.put("target-list-fields",     targetListFields);     // list-fields 전체

	                                relInfo.put("key-mapping", keyMappings);

	                                // CASE 구분 플래그 (relation-entity 존재 여부 기준)
	                                if (relationEntity != null) {
	                                    relInfo.put("rel-type", "many");
	                                } else {
	                                    relInfo.put("rel-type", "one");
	                                }

	                                dti.put("hasRelInfo", true);
	                                dti.put("rel-info", relInfo);

	                            } else {
	                                dti.put("hasRelInfo", false);
	                                dti.put("rel-info", null);
	                            }

	                            //2025-11-19 seyoung : create-flow 추가된 경우 (attach 파일 설정용)
	                            Map<String, Object> createFlow = (Map<String, Object>) dtab.get("create-flow");
	                            if (createFlow != null) {

	                                // 1. main / attach entity 이름
	                                String mainEntity   = (String) createFlow.get("main-entity");
	                                String attachEntity = (String) createFlow.get("attach-entity");

	                                dti.put("hasPreCreateEntity", true);
	                                dti.put("create-main-entity", mainEntity);
	                                dti.put("create-attach-entity", attachEntity);

	                                // 2. key-mapping 리스트 읽기
	                                List<Map<String, Object>> keyMappingList =
	                                    (List<Map<String, Object>>) createFlow.get("key-mapping");

	                                if (keyMappingList != null) {
	                                    List<Map<String, Object>> parsedKeyMappings = keyMappingList.stream()
	                                        .map(m -> {
	                                            String main   = (String) m.get("main");
	                                            String attach = (String) m.get("attach");

	                                            Map<String, Object> out = new HashMap<>();
	                                            out.put("main", main);
	                                            out.put("attach", attach);
	                                            return out;
	                                        })
	                                        .collect(Collectors.toList());

	                                    dti.put("create-flow-key-mapping", parsedKeyMappings);
	                                } else {
	                                    dti.put("hasPreCreateEntity", false);
	                                    dti.put("create-flow-key-mapping", new ArrayList<>());
	                                }
	                            }
	                            
	                            return dti;
	                        })
	                        .collect(Collectors.toList());
	                    tabInfo.put("detail-tab", parsedDetailTabs);
	                } else {
	                    tabInfo.put("detail-tab", new ArrayList<>());
	                }
		            parsedTabs.add(tabInfo);
	            }
	            
	            info.put("tabs", parsedTabs);
	            types.add(info);
		        
	        }

	        data.put("types", types);
	        data.put("typeGb", typeGb);
    	}
        
        return data;
    }
    
    /**
     * typeId에 대한 typeGb를 확인하여 리턴
     * @param typeId
     * @return String typeGb
     */
    public String getTypeGbByTypeId(String typeId) {

        String[] ymlFileNames = { "construction", "facilities" };

        if (StringUtils.isBlank(typeId)) {
            log.error("[UisConfigUtil] getTypeGbByTypeId parameter typeId is blank");
            return ymlFileNames[0];
        }

        for (String fileName : ymlFileNames) {

            InputStream inputStream = null;
            try {
                Resource resource = resourceLoader.getResource(
                        "classpath:uisMap/" + fileName + ".yml");

                if (!resource.exists()) {
                    log.warn("[UisConfigUtil] yml file not found: {}.yml", fileName);
                    continue;
                }

                // 시설물은 프리픽스 제거 (원본 typeId 유지)
                String searchTypeId = typeId;
                if ("facilities".equals(fileName) && searchTypeId.contains(":")) {
                    searchTypeId = searchTypeId.substring(searchTypeId.indexOf(':') + 1);
                }

                inputStream = resource.getInputStream();

                ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
                Map<String, Object> yamlData = mapper.readValue(inputStream, Map.class);

                // 해당 파일의 배열에서 typeId 검색
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> list =
                        (List<Map<String, Object>>) yamlData.get(fileName);

                if (list != null) {
                    for (Map<String, Object> item : list) {
                        String itemId = (String) item.get("id");
                        if (searchTypeId.equals(itemId)) {
                            log.debug("[UisConfigUtil] found id={} in {}.yml", typeId, fileName);
                            return fileName;
                        }
                    }
                }

            } catch (IOException e) {
                log.error("[UisConfigUtil] yml file read error: {}.yml", fileName, e);
                continue;
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException closeEx) {
                        log.error("YAML InputStream close 실패 (무시): fileName={}", fileName, closeEx);
                    }
                }
            }
        }

        // 해당하는 타입을 찾지 못한 경우 기본값 반환
        log.debug("[UisConfigUtil] not found id={} in yml files", typeId);
        return ymlFileNames[0];
    }


    private Map<String, Object> getFieldInfo (List<Map<String, Object>> fieldList)
    {
    	if (fieldList == null || fieldList.isEmpty()) return new HashMap<>();
    	// 필드 정보 추출 및 매핑 정보 생성
        return fieldList.stream()
            .collect(Collectors.toMap(
                field -> (String) field.get("id"),
                field -> {
                    Map<String, Object> info = new HashMap<>();
                    info.put("name", field.get("name"));
                    info.put("type", field.get("type"));
                    info.put("length", field.get("length")); // 2025-10-23 호지원 length 추가
                    if (field.get("options") != null) {
                        info.put("options", field.get("options"));
                        // options가 List<Map> 형태일 경우 code 값 추출
                        List<Map<String, Object>> options = (List<Map<String, Object>>) field.get("options");
                        if (options != null && !options.isEmpty()) {
                            info.put("code", options.get(0).get("code"));
                        }
                    }//2025-10-23 seyoung : combo option 하드코딩
                    else if(field.get("values") != null) {
                    	info.put("values", (List<Map<String, Object>>) field.get("values"));
                    }
                    return info;
                },
                // 중복된 id가 있을 경우 나중에 나온 값으로 덮어쓰기
                (existing, replacement) -> replacement
            ));
    }

}