package incheon.ags.mrb.style.vo;

import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.Valid;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;

/**
 * JSON 스타일 정의 VO
 * JSON to SLD 변환을 위한 스타일 정보
 */
@lombok.Getter
@lombok.Setter
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonStyleVO {

    /** 레이어 ID (워크스페이스:레이어명) */
    @NotBlank(message = "레이어 ID는 필수입니다")
    @Size(max = 200, message = "레이어 ID는 200자를 초과할 수 없습니다")
    private String layerId;

    /** 레이어명 */
    @NotBlank(message = "레이어명은 필수입니다")
    @Size(max = 100, message = "레이어명은 100자를 초과할 수 없습니다")
    private String layerName;

    /** 스타일명 */
    @Size(max = 100, message = "스타일명은 100자를 초과할 수 없습니다")
    private String styleName;

    /** 스타일 서비스명 (프런트에서 전달) */
    @Size(max = 64, message = "스타일 서비스명은 64자를 초과할 수 없습니다")
    private String styleSrvName;

    /** 레이어 타입 */
    @NotBlank(message = "레이어 타입은 필수입니다")
    @Pattern(regexp = "^(point|line|linestring|polygon|multipoint|multilinestring|multipolygon|multiline)$", message = "레이어 타입은 point, line, linestring, polygon, multipoint, multilinestring, multipolygon, multiline 중 하나여야 합니다")
    private String layerType;

    /** 스타일 방식 */
    @NotBlank(message = "스타일 방식은 필수입니다")
    @Pattern(regexp = "^(single|graduated|categorized|rule)$", message = "스타일 방식은 single, graduated, categorized, rule 중 하나여야 합니다")
    private String styleMode;

    /** 표출 레벨 */
    @Valid
    private DisplayLevelConfig displayLevel;

    /** 기본 스타일 설정 */
    @Valid
    private StyleConfig style;

    /** 분류 설정 (classified, graduated 타입용) */
    @Valid
    @JsonProperty(value = "classificationConfig", access = JsonProperty.Access.READ_WRITE)
    @JsonAlias({ "classification", "equalInterval", "quantile", "naturalBreaks" })
    private ClassificationConfig classificationConfig;

    /** 라벨 설정 */
    @Valid
    private LabelConfig labelConfig;

    /** 내보낸 시간 */
    private LocalDateTime exportedAt;

    /** 아이콘 정보 Map (인덱스 → iconId) */
    private java.util.Map<String, Object> iconInfo;

    /**
     * 기본 스타일 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class StyleConfig {

        /** 선 스타일 */
        @Valid
        private StrokeConfig stroke;

        /** 면 스타일 */
        @Valid
        private FillConfig fill;

        /** 심볼 스타일 (Point용) */
        @Valid
        private SymbolConfig symbol;

        /** 마커 설정 (Point용) */
        @Valid
        private MarkerConfig marker;

        /** 라벨 설정 */
        @Valid
        private LabelConfig label;
    }

    /**
     * 선 스타일 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class StrokeConfig {

        /** 선 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 선 두께 */
        @Min(value = 1, message = "선 두께는 1 이상이어야 합니다")
        private Integer width;

        /** 선 스타일 */
        @Pattern(regexp = "^(solid|dashed|dotted)$", message = "선 스타일은 solid, dashed, dotted 중 하나여야 합니다")
        private String style;

        /** 선 투명도 (0.0-1.0) */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double opacity;

        /** 대시 배열 (점선 패턴) */
        private Double[] dashArray;

        /** 선 끝 모양 */
        @Pattern(regexp = "^(butt|round|square)$", message = "선 끝 모양은 butt, round, square 중 하나여야 합니다")
        private String lineCap;

        /** 선 연결 모양 */
        @Pattern(regexp = "^(miter|round|bevel)$", message = "선 연결 모양은 miter, round, bevel 중 하나여야 합니다")
        private String lineJoin;
    }

    /**
     * 면 스타일 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class FillConfig {

        /** 면 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 면 투명도 (0.0-1.0) */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double opacity;
    }

    /**
     * 심볼 스타일 설정 (Point용)
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class SymbolConfig {

        /** 심볼 타입 (circle, square, triangle, userIcon 등) */
        private String type;

        /** 사용자 업로드 아이콘 ID (type이 userIcon일 때 사용) */
        @Size(max = 50, message = "아이콘 ID는 50자를 초과할 수 없습니다")
        private String iconId;

        /** 심볼 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 심볼 크기 */
        @Min(value = 1, message = "심볼 크기는 1 이상이어야 합니다")
        private Integer size;

        /** 라인 두께 (line용 - size 대신 사용) */
        @Min(value = 1, message = "두께는 1 이상이어야 합니다")
        private Integer width;

        /** 심볼 투명도 */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double opacity;

        /** 채우기 패턴 */
        @Pattern(regexp = "^(none|stripe|dot|grid)$", message = "패턴은 none, stripe, dot, grid 중 하나여야 합니다")
        private String fillPattern;

        /** 외곽선 설정 */
        @Valid
        private StrokeConfig stroke;

        /** 이미지 설정 (image 타입용) */
        @Valid
        private ImageConfig image;

        // Legacy 필드들 (하위 호환성)
        /** 심볼 외곽선 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String strokeColor;

        /** 심볼 외곽선 두께 */
        @Min(value = 1, message = "외곽선 두께는 1 이상이어야 합니다")
        private Integer strokeWidth;

        /** 심볼 외곽선 투명도 */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double strokeOpacity;

        /** 심볼 채우기 투명도 */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double fillOpacity;
    }

    /**
     * 이미지 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class ImageConfig {

        /** 이미지 소스 URL */
        @NotBlank(message = "이미지 소스는 필수입니다")
        private String src;

        /** 이미지 스케일 */
        @DecimalMin(value = "0.1", message = "이미지 스케일은 0.1 이상이어야 합니다")
        @DecimalMax(value = "10.0", message = "이미지 스케일은 10.0 이하여야 합니다")
        private Double scale;

        /** 이미지 앵커 포인트 [x, y] */
        @Size(min = 2, max = 2, message = "앵커는 [x, y] 형태의 2개 요소 배열이어야 합니다")
        private Double[] anchor;
    }

    /**
     * 마커 설정 (Point용)
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class MarkerConfig {

        /** 마커 타입 */
        private String type;

        /** 이미지 URL (image 타입용) */
        private String imageUrl;

        /** 이미지 스케일 */
        @DecimalMin(value = "0.1", message = "이미지 스케일은 0.1 이상이어야 합니다")
        @DecimalMax(value = "10.0", message = "이미지 스케일은 10.0 이하여야 합니다")
        private Double imageScale;

        /** 이미지 앵커 X */
        @DecimalMin(value = "0.0", message = "앵커 X는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "앵커 X는 1.0 이하여야 합니다")
        private Double anchorX;

        /** 이미지 앵커 Y */
        @DecimalMin(value = "0.0", message = "앵커 Y는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "앵커 Y는 1.0 이하여야 합니다")
        private Double anchorY;

        /** 마커 크기 */
        @Min(value = 1, message = "마커 크기는 1 이상이어야 합니다")
        private Integer size;
    }

    /**
     * 표출 레벨 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class DisplayLevelConfig {

        /** 최소 줌 레벨 */
        @Min(value = 0, message = "최소 줌 레벨은 0 이상이어야 합니다")
        private Integer minZoom;

        /** 최대 줌 레벨 */
        @Min(value = 0, message = "최대 줌 레벨은 0 이상이어야 합니다")
        private Integer maxZoom;
    }

    /**
     * 분류 설정 (graduated, categorized 방식용)
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class ClassificationConfig {

        /** 분류 사용 여부 */
        private Boolean enabled;

        /** 분류 기준 필드 */
        @Size(max = 100, message = "필드명은 100자를 초과할 수 없습니다")
        private String field;

        /** 분류 방법 */
        @Pattern(regexp = "^(equalInterval|quantile|naturalBreaks)$", message = "분류 방법은 equalInterval, quantile, naturalBreaks 중 하나여야 합니다")
        private String method;

        /** 범위별 스타일 목록 (graduated용) */
        @Valid
        private List<RangeRule> ranges;

        /** 카테고리별 스타일 목록 (categorized용) */
        @Valid
        private List<CategoryRule> categories;

        /** 구간 수 */
        @Min(value = 2, message = "구간 수는 2 이상이어야 합니다")
        private Integer classCount;

        /** 분류 규칙 목록 (rule 방식용) */
        @Valid
        private List<ClassificationRule> rules;

        /** 표출 레벨 (범위별 스타일용) */
        @Valid
        private DisplayLevelConfig displayLevel;
    }

    /**
     * 범위 규칙 (graduated용)
     */
    @lombok.Getter
    @lombok.Setter
    public static class RangeRule {

        /** 최소값 */
        @NotNull(message = "최소값은 필수입니다")
        private Double min;

        /** 최대값 */
        @NotNull(message = "최대값은 필수입니다")
        private Double max;

        /** 라벨 */
        private String label;

        /** 해당 범위의 스타일 */
        @NotNull(message = "스타일은 필수입니다")
        @Valid
        private StyleConfig style;
    }

    /**
     * 카테고리 규칙 (categorized용)
     */
    @lombok.Getter
    @lombok.Setter
    public static class CategoryRule {

        /** 카테고리 값 (NULL 허용: 빈 값 또는 NULL 데이터 처리용) */
        private String value;

        /** 라벨 */
        private String label;

        /** 해당 카테고리의 스타일 */
        @NotNull(message = "스타일은 필수입니다")
        @Valid
        private StyleConfig style;

        /**
         * 값 설정 (문자열 "NULL" 또는 "null"을 실제 null로 변환)
         * 
         * @param value 카테고리 값
         */
        public void setValue(String value) {
            // 문자열 "NULL" 또는 "null"을 실제 null로 변환
            if ("NULL".equals(value) || "null".equals(value)) {
                this.value = null;
            } else {
                this.value = value;
            }
        }
    }

    /**
     * 규칙 기반 분류 (rule용)
     */
    @lombok.Getter
    @lombok.Setter
    public static class ClassificationRule {

        /** 규칙 라벨 */
        @NotBlank(message = "규칙 라벨은 필수입니다")
        @Size(max = 200, message = "규칙 라벨은 200자를 초과할 수 없습니다")
        private String label;

        /** 필터 조건 */
        @NotNull(message = "필터 조건은 필수입니다")
        @Valid
        private FilterConfig filter;

        /** 해당 규칙의 스타일 */
        @NotNull(message = "스타일은 필수입니다")
        @Valid
        private StyleConfig style;
    }

    /**
     * 규칙 목록 설정 (rule 방식용)
     */
    @lombok.Getter
    @lombok.Setter
    public static class RuleConfig {

        /** 규칙명 */
        @NotBlank(message = "규칙명은 필수입니다")
        @Size(max = 100, message = "규칙명은 100자를 초과할 수 없습니다")
        private String name;

        /** 활성화 여부 */
        private Boolean enabled;

        /** 필터 조건 */
        @NotNull(message = "필터 조건은 필수입니다")
        @Valid
        private FilterConfig filter;

        /** 해당 규칙의 스타일 */
        @NotNull(message = "스타일은 필수입니다")
        @Valid
        private StyleConfig style;
    }

    /**
     * 필터 설정
     */
    @lombok.Getter
    @lombok.Setter
    public static class FilterConfig {

        /** 필터 활성화 여부 */
        private Boolean enabled;

        /** 논리 연산자 (복합 조건용) */
        @Pattern(regexp = "^(AND|OR)$", message = "논리 연산자는 AND, OR 중 하나여야 합니다")
        private String operator;

        /** 단순 조건 목록 */
        @Valid
        private List<SimpleCondition> conditions;

        /** 중첩 그룹 조건 */
        @Valid
        private List<FilterConfig> groups;
    }

    /**
     * 단순 조건
     */
    @lombok.Getter
    @lombok.Setter
    public static class SimpleCondition {

        /** 필터 대상 필드 */
        @NotBlank(message = "필드명은 필수입니다")
        @Size(max = 100, message = "필드명은 100자를 초과할 수 없습니다")
        private String field;

        /** 연산자 */
        @NotBlank(message = "연산자는 필수입니다")
        @Pattern(regexp = "^(=|!=|>|<|>=|<=|LIKE|NOT LIKE|IN|NOT IN)$", message = "연산자는 =, !=, >, <, >=, <=, LIKE, NOT LIKE, IN, NOT IN 중 하나여야 합니다")
        private String operator;

        /** 비교값 */
        @NotBlank(message = "비교값은 필수입니다")
        private String value;

        /** 데이터 타입 */
        @Pattern(regexp = "^(string|number|date|boolean)$", message = "데이터 타입은 string, number, date, boolean 중 하나여야 합니다")
        private String dataType;
    }

    /**
     * 라벨 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class LabelConfig {

        /** 라벨 사용 여부 */
        private Boolean enabled;

        /** 라벨 필드명 */
        @Size(max = 100, message = "라벨 필드명은 100자를 초과할 수 없습니다")
        private String field;

        /** 폰트명 (예: "Pretendard", "NanumGothic") */
        @Size(max = 100, message = "폰트명은 100자를 초과할 수 없습니다")
        private String font;

        /** 폰트 크기 (px 단위) */
        @Min(value = 1, message = "폰트 크기는 1 이상이어야 합니다")
        private Integer size;

        /** 라벨 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 헤일로(외곽선) 설정 */
        @Valid
        private HaloConfig halo;

        /** 배경 설정 */
        @Valid
        private BackgroundConfig background;

        /** 라벨 배치 방식 */
        @Pattern(regexp = "^(point|line)$", message = "배치 방식은 point, line 중 하나여야 합니다")
        private String placement;

        /** 라벨 오프셋 [x, y] */
        @Size(min = 2, max = 2, message = "오프셋은 [x, y] 형태의 2개 요소 배열이어야 합니다")
        private Double[] offset;

        /** 최소 줌 레벨 */
        @Min(value = 0, message = "최소 줌 레벨은 0 이상이어야 합니다")
        private Integer minZoom;

        /** 최대 줌 레벨 */
        @Min(value = 0, message = "최대 줌 레벨은 0 이상이어야 합니다")
        private Integer maxZoom;

        // Legacy 필드 (하위 호환성)
        /** 라벨 필드명 (구버전) */
        @Size(max = 100, message = "라벨 필드명은 100자를 초과할 수 없습니다")
        private String column;

        /** 라벨 스타일 (구버전) */
        @Valid
        private LabelStyleConfig style;
    }

    /**
     * 헤일로(외곽선) 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class HaloConfig {

        /** 헤일로 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 헤일로 두께 */
        @Min(value = 0, message = "헤일로 두께는 0 이상이어야 합니다")
        private Integer width;
    }

    /**
     * 배경 설정
     */
    @lombok.Getter
    @lombok.Setter
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class BackgroundConfig {

        /** 배경 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 배경 투명도 (0.0-1.0) */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double opacity;
    }

    /**
     * 라벨 스타일 설정
     */
    @lombok.Getter
    @lombok.Setter
    public static class LabelStyleConfig {

        /** 폰트 크기 */
        @Min(value = 8, message = "폰트 크기는 8 이상이어야 합니다")
        private Integer fontSize;

        /** 폰트 패밀리 */
        @Size(max = 50, message = "폰트 패밀리는 50자를 초과할 수 없습니다")
        private String fontFamily;

        /** 글자 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String color;

        /** 글자 투명도 */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double opacity;

        /** 외곽선 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String outlineColor;

        /** 외곽선 두께 */
        @Min(value = 1, message = "외곽선 두께는 1 이상이어야 합니다")
        private Integer outlineWidth;

        /** 외곽선 투명도 */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double outlineOpacity;

        /** 배경 색상 */
        private String backgroundColor;

        /** 배경 투명도 */
        @DecimalMin(value = "0.0", message = "투명도는 0.0 이상이어야 합니다")
        @DecimalMax(value = "1.0", message = "투명도는 1.0 이하여야 합니다")
        private Double backgroundOpacity;

        /** 배경 테두리 색상 */
        @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "색상은 #RRGGBB 형식이어야 합니다")
        private String backgroundBorderColor;

        /** 배경 테두리 두께 */
        @Min(value = 1, message = "배경 테두리 두께는 1 이상이어야 합니다")
        private Integer backgroundBorderWidth;

        /** X 오프셋 */
        private Integer offsetX;

        /** Y 오프셋 */
        private Integer offsetY;

        /** 패딩 */
        @Min(value = 0, message = "패딩은 0 이상이어야 합니다")
        private Integer padding;
    }

    /**
     * 워크스페이스 추출
     */
    public String getWorkspace() {
        if (layerId != null && layerId.contains(":")) {
            return layerId.substring(0, layerId.indexOf(":"));
        }
        return null;
    }

}