package incheon.ags.mrb.style.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import incheon.ags.mrb.main.mapper.UserIconMapper;
import incheon.ags.mrb.main.service.RecipeFileService;
import incheon.ags.mrb.main.vo.UserIconVO;
import incheon.ags.mrb.style.vo.JsonStyleVO;

import javax.annotation.Resource;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import static incheon.ags.mrb.style.utils.SldConstants.*;
import static incheon.ags.mrb.style.utils.SldElementBuilder.*;

import java.io.StringWriter;
import java.util.List;

/**
 * JSON 스타일을 SLD XML로 변환하는 유틸리티 클래스
 * OGC SLD 1.0.0 표준 준수
 */
@Component
public class StyleConverter {

    @Resource(name = "userIconMapper")
    private UserIconMapper userIconMapper;

    @Resource(name = "recipeFileService")
    private RecipeFileService recipeFileService;

    @Value("${Globals.resources.api.url}")
    private String apiUrl;

    /**
     * JSON 스타일을 SLD XML로 변환
     */
    public String convertToSld(JsonStyleVO jsonStyle) throws Exception {
        Document doc = createDocument();
        Element sldRoot = createSldRoot(doc, jsonStyle);
        doc.appendChild(sldRoot);

        return documentToString(doc);
    }

    /**
     * XML Document 생성
     */
    private Document createDocument() throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.newDocument();
    }

    /**
     * SLD 루트 엘리먼트 생성
     */
    private Element createSldRoot(Document doc, JsonStyleVO jsonStyle) {
        // StyledLayerDescriptor 루트
        Element sldRoot = doc.createElementNS(SLD_NAMESPACE, "StyledLayerDescriptor");
        sldRoot.setAttribute("version", "1.0.0");
        sldRoot.setAttribute("xmlns", SLD_NAMESPACE);
        sldRoot.setAttribute("xmlns:ogc", OGC_NAMESPACE);
        sldRoot.setAttribute("xmlns:xsi", XSI_NAMESPACE);
        sldRoot.setAttribute("xmlns:xlink", XLINK_NAMESPACE);
        sldRoot.setAttribute("xsi:schemaLocation",
                "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd");

        // NamedLayer
        Element namedLayer = createSld(doc, "NamedLayer").build();
        sldRoot.appendChild(namedLayer);

        // NamedLayer의 Name은 빈 문자열로 설정 (레이어 이름 불필요)
        Element name = createSld(doc, "Name").text("").build();
        namedLayer.appendChild(name);

        // UserStyle
        Element userStyle = createSld(doc, "UserStyle").build();
        namedLayer.appendChild(userStyle);

        String styleNameValue = getStyleName(jsonStyle);
        userStyle.appendChild(createSld(doc, "Name").text(styleNameValue).build());
        userStyle.appendChild(createSld(doc, "Title").text(styleNameValue).build());

        // FeatureTypeStyle
        Element featureTypeStyle = createSld(doc, "FeatureTypeStyle").build();
        userStyle.appendChild(featureTypeStyle);

        // styleMode에 따른 Rule 생성
        String styleMode = jsonStyle.getStyleMode() != null ? jsonStyle.getStyleMode() : "single";
        createRulesByMode(doc, featureTypeStyle, jsonStyle, styleMode);

        return sldRoot;
    }

    /**
     * 스타일명 추출
     */
    private String getStyleName(JsonStyleVO jsonStyle) {
        String styleNameValue = jsonStyle.getStyleName();
        if (styleNameValue == null || styleNameValue.trim().isEmpty()) {
            String layerName = jsonStyle.getLayerName() != null ? jsonStyle.getLayerName() : "";
            styleNameValue = layerName + "_style";
        }
        return styleNameValue;
    }

    /**
     * styleMode에 따라 Rule 생성 라우팅
     */
    private void createRulesByMode(Document doc, Element featureTypeStyle, JsonStyleVO jsonStyle, String styleMode) {
        switch (styleMode) {
            case "categorized":
                createCategorizedRules(doc, featureTypeStyle, jsonStyle);
                break;
            case "graduated":
                createGraduatedRules(doc, featureTypeStyle, jsonStyle);
                break;
            case "rule":
                createRuleBasedRules(doc, featureTypeStyle, jsonStyle);
                break;
            default:
                createSingleRule(doc, featureTypeStyle, jsonStyle);
        }
    }

    // ==================== Single Rule ====================

    /**
     * 단일 스타일 Rule 생성
     */
    private void createSingleRule(Document doc, Element featureTypeStyle, JsonStyleVO jsonStyle) {
        Element rule = createSld(doc, "Rule").build();
        featureTypeStyle.appendChild(rule);

        // Rule의 Name을 스타일 이름으로 설정
        String styleNameValue = getStyleName(jsonStyle);
        rule.appendChild(createSld(doc, "Name").text(styleNameValue).build());

        addScaleConstraints(doc, rule, jsonStyle);
        addSymbolizerByLayerType(doc, rule, jsonStyle, jsonStyle.getStyle());
        createTextSymbolizer(doc, rule, jsonStyle);
    }

    // ==================== Categorized Rules ====================

    /**
     * 분류값 스타일 Rules 생성
     */
    private void createCategorizedRules(Document doc, Element featureTypeStyle, JsonStyleVO jsonStyle) {
        if (jsonStyle.getClassificationConfig() == null ||
                jsonStyle.getClassificationConfig().getCategories() == null ||
                jsonStyle.getClassificationConfig().getCategories().isEmpty()) {
            createSingleRule(doc, featureTypeStyle, jsonStyle);
            return;
        }

        String fieldName = jsonStyle.getClassificationConfig().getField() != null
                ? jsonStyle.getClassificationConfig().getField()
                : "type";

        for (JsonStyleVO.CategoryRule category : jsonStyle.getClassificationConfig().getCategories()) {
            Element rule = createSld(doc, "Rule").build();
            featureTypeStyle.appendChild(rule);

            // Rule 이름: NULL / 빈 값 / 일반 값 처리
            String ruleName;
            if (category.getLabel() != null) {
                ruleName = category.getLabel();
            } else if (category.getValue() != null) {
                ruleName = category.getValue().isEmpty() ? "(빈 값)" : category.getValue();
            } else {
                ruleName = "NULL";
            }
            rule.appendChild(createSld(doc, "Name").text(ruleName).build());
            addScaleConstraints(doc, rule, jsonStyle);

            // 필터 조건 추가 (NULL / 빈 값 / 일반 값 처리)
            if (category.getValue() != null) {
                // 일반 값 또는 빈 값: PropertyIsEqualTo 필터 사용
                addEqualityFilter(doc, rule, fieldName, category.getValue());
            } else {
                // NULL 값: PropertyIsNull 필터 사용
                addNullFilter(doc, rule, fieldName);
            }

            addSymbolizerByLayerType(doc, rule, jsonStyle, category.getStyle());
            createTextSymbolizerForCategory(doc, rule, jsonStyle, category);
        }
    }

    // ==================== Graduated Rules ====================

    /**
     * 범위별 스타일 Rules 생성
     */
    private void createGraduatedRules(Document doc, Element featureTypeStyle, JsonStyleVO jsonStyle) {
        if (jsonStyle.getClassificationConfig() == null ||
                jsonStyle.getClassificationConfig().getRanges() == null ||
                jsonStyle.getClassificationConfig().getRanges().isEmpty()) {
            createSingleRule(doc, featureTypeStyle, jsonStyle);
            return;
        }

        String fieldName = jsonStyle.getClassificationConfig().getField() != null
                ? jsonStyle.getClassificationConfig().getField()
                : "value";

        List<JsonStyleVO.RangeRule> ranges = jsonStyle.getClassificationConfig().getRanges();
        int totalRanges = ranges.size();

        // 첫 번째 Rule에 분류방법 저장 (Description 사용)
        String classificationMethod = jsonStyle.getClassificationConfig().getMethod();
        if (classificationMethod == null || classificationMethod.trim().isEmpty()) {
            classificationMethod = "equalInterval"; // 기본값
        }

        for (int i = 0; i < totalRanges; i++) {
            JsonStyleVO.RangeRule range = ranges.get(i);
            boolean isLastRange = (i == totalRanges - 1);

            Element rule = createSld(doc, "Rule").build();
            featureTypeStyle.appendChild(rule);

            String ruleName = String.format("%s - %s",
                    range.getMin() != null ? range.getMin().toString() : "MIN",
                    range.getMax() != null ? range.getMax().toString() : "MAX");
            rule.appendChild(createSld(doc, "Name").text(ruleName).build());

            // 첫 번째 Rule에만 분류방법 저장
            if (i == 0) {
                Element description = createSld(doc, "Description").build();
                Element title = createSld(doc, "Title").text("classificationMethod:" + classificationMethod).build();
                description.appendChild(title);
                rule.appendChild(description);
            }

            addScaleConstraints(doc, rule, jsonStyle);

            // 범위 필터 추가 (마지막 구간은 <= 사용)
            addRangeFilter(doc, rule, fieldName, range.getMin(), range.getMax(), isLastRange);

            addSymbolizerByLayerType(doc, rule, jsonStyle, range.getStyle());
            // 범위별 라벨 설정이 있으면 우선 사용, 없으면 전체 스타일의 라벨 사용
            createTextSymbolizerForRange(doc, rule, jsonStyle, range);
        }
    }

    // ==================== Rule-based Rules ====================

    /**
     * 규칙 기반 스타일 Rules 생성
     */
    private void createRuleBasedRules(Document doc, Element featureTypeStyle, JsonStyleVO jsonStyle) {
        if (jsonStyle.getClassificationConfig() == null ||
                jsonStyle.getClassificationConfig().getRules() == null ||
                jsonStyle.getClassificationConfig().getRules().isEmpty()) {
            createSingleRule(doc, featureTypeStyle, jsonStyle);
            return;
        }

        for (JsonStyleVO.ClassificationRule ruleConfig : jsonStyle.getClassificationConfig().getRules()) {
            Element rule = createSld(doc, "Rule").build();
            featureTypeStyle.appendChild(rule);

            String ruleName = ruleConfig.getLabel() != null ? ruleConfig.getLabel() : "custom_rule";
            rule.appendChild(createSld(doc, "Name").text(ruleName).build());
            addScaleConstraints(doc, rule, jsonStyle);

            // TODO: 복잡한 필터 조건 처리는 추후 확장
            addDefaultSymbolizer(doc, rule, jsonStyle.getLayerType());
            createTextSymbolizer(doc, rule, jsonStyle);
        }
    }

    // ==================== 공통 유틸리티 메서드 ====================

    /**
     * 레이어 타입에 따라 Symbolizer 추가
     */
    private void addSymbolizerByLayerType(Document doc, Element rule, JsonStyleVO jsonStyle,
            JsonStyleVO.StyleConfig styleConfig) {
        if (styleConfig == null)
            return;

        String layerType = jsonStyle.getLayerType() != null ? jsonStyle.getLayerType().toLowerCase() : "";

        if (layerType.contains("point")) {
            if (styleConfig.getSymbol() != null) {
                String iconUrl = getIconUrl(styleConfig.getSymbol());
                SymbolizerFactory.createPointSymbolizer(doc, rule, styleConfig.getSymbol(), iconUrl);
            }
        } else if (layerType.contains("line") || layerType.equals("multiline")) {
            JsonStyleVO.StrokeConfig strokeConfig = getStrokeConfig(styleConfig);
            if (strokeConfig != null) {
                SymbolizerFactory.createLineSymbolizer(doc, rule, strokeConfig);
            }
        } else if (layerType.contains("polygon")) {
            SymbolizerFactory.createPolygonSymbolizer(doc, rule, styleConfig.getSymbol(), styleConfig.getFill());
        }
    }

    /**
     * 사용자 업로드 아이콘 URL 조회 (GeoServer용 절대 URL 반환)
     */
    private String getIconUrl(JsonStyleVO.SymbolConfig symbol) {
        if (symbol == null || !"userIcon".equals(symbol.getType()) || symbol.getIconId() == null) {
            return null;
        }

        // iconId를 Integer로 변환
        Integer iconId = Integer.parseInt(symbol.getIconId());

        // DB에서 아이콘 정보 조회
        UserIconVO searchVO = new UserIconVO();
        searchVO.setIconId(iconId);
        UserIconVO iconVO = userIconMapper.selectUserIconById(searchVO);

        if (iconVO != null && iconVO.getIconPath() != null) {
            // DB에 저장된 파일 시스템 경로를 /resources/ URL로 변환
            String resourceUrl = recipeFileService.convertToResourceUrl(iconVO.getIconPath());
            // GeoServer가 접근할 수 있도록 서버 주소를 포함한 절대 URL 반환
            return apiUrl + resourceUrl;
        }

        return null;
    }

    /**
     * StrokeConfig 추출 (우선순위: style.stroke > style.symbol.stroke)
     */
    private JsonStyleVO.StrokeConfig getStrokeConfig(JsonStyleVO.StyleConfig styleConfig) {
        if (styleConfig.getStroke() != null) {
            return styleConfig.getStroke();
        }
        if (styleConfig.getSymbol() != null && styleConfig.getSymbol().getStroke() != null) {
            return styleConfig.getSymbol().getStroke();
        }
        return null;
    }

    /**
     * 기본 Symbolizer 추가 (Rule-based 폴백용)
     */
    private void addDefaultSymbolizer(Document doc, Element rule, String layerType) {
        String type = layerType != null ? layerType.toLowerCase() : "";

        if (type.contains("point")) {
            createDefaultPointSymbolizer(doc, rule);
        } else if (type.contains("line") || type.equals("multiline")) {
            createDefaultLineSymbolizer(doc, rule);
        } else if (type.contains("polygon")) {
            createDefaultPolygonSymbolizer(doc, rule);
        }
    }

    /**
     * 기본 Point Symbolizer
     */
    private void createDefaultPointSymbolizer(Document doc, Element rule) {
        Element pointSymbolizer = createSld(doc, "PointSymbolizer").build();
        rule.appendChild(pointSymbolizer);

        Element graphic = createSld(doc, "Graphic").build();
        pointSymbolizer.appendChild(graphic);

        Element mark = createSld(doc, "Mark").build();
        graphic.appendChild(mark);

        mark.appendChild(createSld(doc, "WellKnownName").text("circle").build());

        SldElementBuilder fill = createSld(doc, "Fill").cssParam("fill", "#0000FF");
        mark.appendChild(fill.build());

        graphic.appendChild(createSld(doc, "Size").text("8").build());
    }

    /**
     * 기본 Line Symbolizer
     */
    private void createDefaultLineSymbolizer(Document doc, Element rule) {
        Element lineSymbolizer = createSld(doc, "LineSymbolizer").build();
        rule.appendChild(lineSymbolizer);

        SldElementBuilder stroke = createSld(doc, "Stroke")
                .cssParam("stroke", "#0000FF")
                .cssParam("stroke-width", "1");
        lineSymbolizer.appendChild(stroke.build());
    }

    /**
     * 기본 Polygon Symbolizer
     */
    private void createDefaultPolygonSymbolizer(Document doc, Element rule) {
        Element polygonSymbolizer = createSld(doc, "PolygonSymbolizer").build();
        rule.appendChild(polygonSymbolizer);

        SldElementBuilder fill = createSld(doc, "Fill")
                .cssParam("fill", "#0000FF")
                .cssParam("fill-opacity", "0.5");
        polygonSymbolizer.appendChild(fill.build());
    }

    /**
     * 스케일 제약 조건 추가
     */
    private void addScaleConstraints(Document doc, Element rule, JsonStyleVO jsonStyle) {
        if (jsonStyle.getDisplayLevel() == null)
            return;

        Integer minZoom = jsonStyle.getDisplayLevel().getMinZoom();
        Integer maxZoom = jsonStyle.getDisplayLevel().getMaxZoom();

        if (minZoom != null) {
            Element maxScale = createSld(doc, "MaxScaleDenominator")
                    .text(String.valueOf(zoomToScaleDenominator(minZoom)))
                    .build();
            rule.appendChild(maxScale);
        }

        if (maxZoom != null) {
            Element minScale = createSld(doc, "MinScaleDenominator")
                    .text(String.valueOf(zoomToScaleDenominator(maxZoom)))
                    .build();
            rule.appendChild(minScale);
        }
    }

    /**
     * 동등 비교 필터 추가 (PropertyIsEqualTo)
     */
    private void addEqualityFilter(Document doc, Element rule, String fieldName, String value) {
        Element filter = createOgc(doc, "Filter").build();
        rule.appendChild(filter);

        Element propertyIsEqualTo = createOgc(doc, "PropertyIsEqualTo").build();
        filter.appendChild(propertyIsEqualTo);

        propertyIsEqualTo.appendChild(createOgc(doc, "PropertyName").text(fieldName).build());
        propertyIsEqualTo.appendChild(createOgc(doc, "Literal").text(value).build());
    }

    /**
     * NULL 값 필터 추가 (PropertyIsNull)
     */
    private void addNullFilter(Document doc, Element rule, String fieldName) {
        Element filter = createOgc(doc, "Filter").build();
        rule.appendChild(filter);

        Element propertyIsNull = createOgc(doc, "PropertyIsNull").build();
        filter.appendChild(propertyIsNull);

        propertyIsNull.appendChild(createOgc(doc, "PropertyName").text(fieldName).build());
    }

    /**
     * 범위 필터 추가 (And + PropertyIsGreaterThanOrEqualTo +
     * PropertyIsLessThan/PropertyIsLessThanOrEqualTo)
     * 
     * @param isLastRange 마지막 구간 여부 (true면 <= 사용, false면 < 사용)
     */
    private void addRangeFilter(Document doc, Element rule, String fieldName, Number min, Number max,
            boolean isLastRange) {
        Element filter = createOgc(doc, "Filter").build();
        rule.appendChild(filter);

        Element and = createOgc(doc, "And").build();
        filter.appendChild(and);

        if (min != null) {
            Element greaterThanOrEqual = createOgc(doc, "PropertyIsGreaterThanOrEqualTo").build();
            and.appendChild(greaterThanOrEqual);

            greaterThanOrEqual.appendChild(createOgc(doc, "PropertyName").text(fieldName).build());
            greaterThanOrEqual.appendChild(createOgc(doc, "Literal").text(min.toString()).build());
        }

        if (max != null) {
            // 마지막 구간은 PropertyIsLessThanOrEqualTo, 나머지는 PropertyIsLessThan
            String comparisonOperator = isLastRange ? "PropertyIsLessThanOrEqualTo" : "PropertyIsLessThan";
            Element comparison = createOgc(doc, comparisonOperator).build();
            and.appendChild(comparison);

            comparison.appendChild(createOgc(doc, "PropertyName").text(fieldName).build());
            comparison.appendChild(createOgc(doc, "Literal").text(max.toString()).build());
        }
    }

    /**
     * TextSymbolizer 생성 (라벨)
     */
    private void createTextSymbolizer(Document doc, Element rule, JsonStyleVO jsonStyle) {
        // 라벨 설정 검증
        boolean hasLabel = jsonStyle.getStyle() != null
                && jsonStyle.getStyle().getLabel() != null
                && jsonStyle.getStyle().getLabel().getField() != null
                && !jsonStyle.getStyle().getLabel().getField().trim().isEmpty();

        if (!hasLabel)
            return;

        buildTextSymbolizer(doc, rule, jsonStyle.getStyle().getLabel());
    }

    /**
     * 범위별 TextSymbolizer 생성 (범위별 라벨 우선 사용)
     */
    private void createTextSymbolizerForRange(Document doc, Element rule, JsonStyleVO jsonStyle,
            JsonStyleVO.RangeRule range) {
        // 범위별 라벨 설정 확인
        JsonStyleVO.LabelConfig labelConfig = null;

        if (range.getStyle() != null && range.getStyle().getLabel() != null
                && range.getStyle().getLabel().getField() != null
                && !range.getStyle().getLabel().getField().trim().isEmpty()) {
            // 범위별 라벨 설정 사용
            labelConfig = range.getStyle().getLabel();
        } else if (jsonStyle.getStyle() != null && jsonStyle.getStyle().getLabel() != null
                && jsonStyle.getStyle().getLabel().getField() != null
                && !jsonStyle.getStyle().getLabel().getField().trim().isEmpty()) {
            // 전체 스타일의 라벨 설정 사용
            labelConfig = jsonStyle.getStyle().getLabel();
        }

        if (labelConfig == null)
            return;

        buildTextSymbolizer(doc, rule, labelConfig);
    }

    /**
     * 카테고리별 TextSymbolizer 생성 (카테고리별 라벨 우선 사용)
     */
    private void createTextSymbolizerForCategory(Document doc, Element rule, JsonStyleVO jsonStyle,
            JsonStyleVO.CategoryRule category) {
        // 카테고리별 라벨 설정 확인
        JsonStyleVO.LabelConfig labelConfig = null;

        if (category.getStyle() != null && category.getStyle().getLabel() != null
                && category.getStyle().getLabel().getField() != null
                && !category.getStyle().getLabel().getField().trim().isEmpty()) {
            // 카테고리별 라벨 설정 사용
            labelConfig = category.getStyle().getLabel();
        } else if (jsonStyle.getStyle() != null && jsonStyle.getStyle().getLabel() != null
                && jsonStyle.getStyle().getLabel().getField() != null
                && !jsonStyle.getStyle().getLabel().getField().trim().isEmpty()) {
            // 전체 스타일의 라벨 설정 사용 (Fallback)
            labelConfig = jsonStyle.getStyle().getLabel();
        }

        if (labelConfig == null)
            return;

        buildTextSymbolizer(doc, rule, labelConfig);
    }

    /**
     * TextSymbolizer 빌드 (공통 로직)
     */
    private void buildTextSymbolizer(Document doc, Element rule, JsonStyleVO.LabelConfig labelConfig) {
        Element textSymbolizer = createSld(doc, "TextSymbolizer").build();
        rule.appendChild(textSymbolizer);

        // Label - 필드명 동적 설정
        Element labelElement = createSld(doc, "Label").build();
        textSymbolizer.appendChild(labelElement);

        String fieldName = labelConfig.getField() != null ? labelConfig.getField() : "name";
        Element propertyName = createOgc(doc, "PropertyName").text(fieldName).build();
        labelElement.appendChild(propertyName);

        // Font
        addFont(doc, textSymbolizer, labelConfig);

        // LabelPlacement
        addLabelPlacement(doc, textSymbolizer, labelConfig);

        // Halo
        if (labelConfig.getHalo() != null) {
            addHalo(doc, textSymbolizer, labelConfig.getHalo());
        }

        // Fill (텍스트 색상)
        String textColor = labelConfig.getColor() != null ? labelConfig.getColor() : "#000000";
        SldElementBuilder fill = createSld(doc, "Fill").cssParam("fill", textColor);
        textSymbolizer.appendChild(fill.build());

        // Background (VendorOption 사용 - GeoServer 확장)
        if (labelConfig.getBackground() != null) {
            addBackgroundVendorOption(doc, textSymbolizer, labelConfig.getBackground());
        }
    }

    /**
     * Background VendorOption 추가 (GeoServer 확장)
     *
     * 주의: SLD 1.0.0 표준에는 TextSymbolizer 배경 속성이 없습니다.
     * - GeoServer의 경우 VendorOption을 사용하여 확장 가능
     * - Graphic을 사용하여 배경 박스 구현
     */
    private void addBackgroundVendorOption(Document doc, Element textSymbolizer,
            JsonStyleVO.BackgroundConfig background) {
        if (background == null || background.getColor() == null)
            return;

        // Graphic 요소 생성
        Element graphic = createSld(doc, "Graphic").build();
        textSymbolizer.appendChild(graphic);

        // Mark로 사각형 배경 생성
        Element mark = createSld(doc, "Mark").build();
        graphic.appendChild(mark);

        // 사각형 모양
        Element wellKnownName = createSld(doc, "WellKnownName").text("square").build();
        mark.appendChild(wellKnownName);

        // 배경 색상
        SldElementBuilder fill = createSld(doc, "Fill")
                .cssParam("fill", background.getColor());

        if (background.getOpacity() != null) {
            double opacity = background.getOpacity() > 1.0 ? background.getOpacity() / 100.0 : background.getOpacity();
            fill.cssParam("fill-opacity", String.format("%.2f", opacity));
        }

        mark.appendChild(fill.build());

        // 배경 크기 (텍스트보다 약간 크게)
        graphic.appendChild(createSld(doc, "Size").text("16").build());

        // GeoServer VendorOption으로 배경 마진 설정
        Element marginOption = doc.createElement("VendorOption");
        marginOption.setAttribute("name", "graphic-margin");
        marginOption.setTextContent("2");
        textSymbolizer.appendChild(marginOption);
    }

    /**
     * Font 추가
     */
    private void addFont(Document doc, Element textSymbolizer, JsonStyleVO.LabelConfig labelConfig) {
        // 폰트명과 크기를 개별 필드에서 가져오기
        String fontFamily = labelConfig.getFont() != null ? labelConfig.getFont() : "Pretendard";
        Integer fontSize = labelConfig.getSize() != null ? labelConfig.getSize() : 14;

        SldElementBuilder font = createSld(doc, "Font")
                .cssParam("font-family", fontFamily)
                .cssParam("font-size", String.valueOf(fontSize));

        textSymbolizer.appendChild(font.build());
    }

    /**
     * LabelPlacement 추가 (offset이 있을 때만)
     */
    private void addLabelPlacement(Document doc, Element textSymbolizer, JsonStyleVO.LabelConfig labelConfig) {
        // offset이 없으면 LabelPlacement 자체를 추가하지 않음
        if (labelConfig.getOffset() == null || labelConfig.getOffset().length != 2) {
            return;
        }

        Element labelPlacement = createSld(doc, "LabelPlacement").build();
        textSymbolizer.appendChild(labelPlacement);

        Element pointPlacement = createSld(doc, "PointPlacement").build();
        labelPlacement.appendChild(pointPlacement);

        Element displacement = createSld(doc, "Displacement").build();
        pointPlacement.appendChild(displacement);

        displacement
                .appendChild(createSld(doc, "DisplacementX").text(String.valueOf(labelConfig.getOffset()[0])).build());
        displacement
                .appendChild(createSld(doc, "DisplacementY").text(String.valueOf(labelConfig.getOffset()[1])).build());
    }

    /**
     * Halo 추가
     */
    private void addHalo(Document doc, Element textSymbolizer, JsonStyleVO.HaloConfig haloConfig) {
        Integer haloWidth = haloConfig.getWidth() != null ? haloConfig.getWidth() : 2;
        String haloColor = haloConfig.getColor() != null ? haloConfig.getColor() : "#FFFFFF";

        Element halo = createSld(doc, "Halo").build();
        textSymbolizer.appendChild(halo);

        halo.appendChild(createSld(doc, "Radius").text(String.valueOf(haloWidth)).build());

        SldElementBuilder haloFill = createSld(doc, "Fill").cssParam("fill", haloColor);
        halo.appendChild(haloFill.build());
    }

    /**
     * XML Document를 문자열로 변환
     */
    private String documentToString(Document doc) throws Exception {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();

        transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

        Transformer transformer = transformerFactory.newTransformer();

        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

        try (StringWriter writer = new StringWriter()) {
            transformer.transform(new DOMSource(doc), new StreamResult(writer));
            return writer.toString();
        }
    }
}
