"""옹벽 (Retaining Wall) 전용 DXF 파서.

구조 특성:
- 선형 옹벽 경로 (긴 길이방향)
- 구간별로 다른 높이 (지형에 따라 변화)
- 배면(뒤쪽) 앵커바 격자 배치
- 상단 안전난간/파라펫
- 기초부 (base slab, 넓음)
- 수축이음 (균등 간격)
- 배수공 (weep hole)

사용법:
    p = parse_retaining_wall(dxf_paths)
"""

from __future__ import annotations

import re
import math
from dataclasses import dataclass, field

import ezdxf

from view_detector import detect_view_regions
from dxf_geometry import extract_structural_geometry
from view_reconstructor import compute_oriented_bbox


# ---------------------------------------------------------------------------
# 데이터 클래스
# ---------------------------------------------------------------------------

@dataclass
class WallSection:
    """옹벽 구간 하나."""
    start_station: float = 0.0      # 시점 측점 (m)
    end_station: float = 0.0        # 종점 측점
    top_el: float = 0.0             # 상단 EL
    bottom_el: float = 0.0          # 바닥 EL
    # 단면 치수
    top_width: float = 0.5
    bottom_width: float = 2.5
    front_batter: float = 0.02      # 전면 경사 (1:N 비율 중 1 부분)


@dataclass
class RetainingWallParams:
    """옹벽 파라미터."""
    # 전체 선형
    total_length: float = 100.0      # 총 연장 (m)
    path_direction: str = "X"        # "X" 또는 "Y" (주 방향)

    # 높이 범위
    top_el: float = 60.0
    bottom_el: float = 41.5          # 기초 바닥

    # 단면 (평균)
    avg_top_width: float = 0.6       # 상단 폭
    avg_bottom_width: float = 3.0    # 하단 폭 (중간)
    base_slab_width: float = 5.0     # 기초 slab 폭
    base_slab_thickness: float = 1.0 # 기초 두께
    front_batter_ratio: float = 0.05 # 전면 경사 (1:20 수준)

    # 구간별 (상세 있을 경우)
    sections: list = field(default_factory=list)

    # 앵커바
    has_anchors: bool = True
    anchor_count: int = 78
    anchor_spacing_h: float = 3.0    # 수평 간격
    anchor_spacing_v: float = 3.0    # 수직 간격
    anchor_diameter: float = 0.05    # 앵커바 직경 (50mm)
    anchor_length: float = 12.0      # 앵커 매입 길이
    anchor_angle_deg: float = 15     # 하향 각도

    # 상단 안전난간
    has_parapet: bool = True
    parapet_height: float = 1.1
    parapet_thickness: float = 0.15

    # 수축이음
    has_contraction_joints: bool = True
    joint_spacing: float = 10.0      # 간격

    # 배수공
    has_weep_holes: bool = True
    weep_hole_spacing: float = 3.0
    weep_hole_diameter: float = 0.1

    # 기타
    ground_level: float = 41.5       # 전면 지반 EL

    source_files: list = field(default_factory=list)
    raw_annotations: list = field(default_factory=list)

    def total_height(self) -> float:
        return self.top_el - self.bottom_el

    def summary(self) -> str:
        return (
            f"Retaining Wall: L={self.total_length:.1f}m, "
            f"EL.{self.bottom_el:.1f}~{self.top_el:.1f} (H={self.total_height():.1f}m)\n"
            f"  단면 상단={self.avg_top_width:.2f}m, 하단={self.avg_bottom_width:.2f}m, "
            f"기초 slab {self.base_slab_width:.1f}×{self.base_slab_thickness:.1f}m\n"
            f"  앵커 {self.anchor_count}개 ({self.anchor_spacing_h:.1f}×{self.anchor_spacing_v:.1f}m), "
            f"난간 {'O' if self.has_parapet else 'X'}"
        )


# ---------------------------------------------------------------------------
# 파서
# ---------------------------------------------------------------------------

EL_PATTERN = re.compile(r"EL\.?\s*[=:]?\s*(\d+\.?\d*)", re.IGNORECASE)


class RetainingWallParser:

    def parse(self, dxf_paths: list[str]) -> RetainingWallParams:
        params = RetainingWallParams()
        params.source_files = list(dxf_paths)

        for path in dxf_paths:
            try:
                self._parse_single(path, params)
            except Exception as e:
                print(f"  파싱 오류: {e}")

        self._finalize(params)
        return params

    def _parse_single(self, path: str, params: RetainingWallParams):
        doc = ezdxf.readfile(path)
        msp = doc.modelspace()
        geom = extract_structural_geometry(path)
        scale = geom.unit_scale

        views = detect_view_regions(path)

        # EL 수집
        els = []
        for e in msp:
            if e.dxftype() not in ("TEXT", "MTEXT"):
                continue
            try:
                txt = e.dxf.text if e.dxftype() == "TEXT" else (e.text or "")
                m = EL_PATTERN.search(txt)
                if m:
                    pos = e.dxf.insert
                    els.append((pos.x * scale, pos.y * scale, float(m.group(1))))
            except Exception:
                pass

        if els:
            ev = [v for _, _, v in els]
            params.top_el = max(params.top_el, *ev)
            params.bottom_el = min(params.bottom_el, *ev)
            params.raw_annotations.extend(
                [(f"EL.{v:.2f}", x, y) for x, y, v in els]
            )

        # 평면도에서 총 연장 추정
        plan_view = None
        for v in views:
            if v.view_type == "plan":
                plan_view = v
                break

        if plan_view:
            # 평면도 안의 shapes 점들 수집 → PCA로 길이 추정
            local_shapes = plan_view.get_local_shapes()
            all_pts = []
            for s in local_shapes:
                all_pts.extend(s.points)

            if len(all_pts) >= 5:
                obb = compute_oriented_bbox(all_pts)
                if obb and obb["aspect_ratio"] >= 1.5:
                    params.total_length = max(params.total_length, obb["length"])
                    params.path_direction = "X" if abs(obb["axis_long"][0]) > abs(obb["axis_long"][1]) else "Y"

        # 앵커바 개수 추정 (B_Dot 블록 사용)
        anchor_block_count = 0
        for e in msp.query("INSERT"):
            name = getattr(e.dxf, "name", "")
            if "B_Dot" in name or "anchor" in name.lower():
                anchor_block_count += 1
        if anchor_block_count > 10:
            params.anchor_count = anchor_block_count

        # 정면도 영역에서 앵커 격자 확인
        front_view = None
        for v in views:
            if v.view_type == "front":
                front_view = v
                break
        if front_view:
            # 정면도 면적 / 앵커개수로 간격 추정
            area = front_view.width * front_view.height
            if params.anchor_count > 0:
                spacing_approx = math.sqrt(area / params.anchor_count)
                if 1.0 <= spacing_approx <= 6.0:
                    params.anchor_spacing_h = spacing_approx
                    params.anchor_spacing_v = spacing_approx

    def _finalize(self, p: RetainingWallParams):
        # 기초 slab 폭: 본체 하단 폭의 1.5배 정도
        p.base_slab_width = max(p.avg_bottom_width * 1.5, 4.0)
        # 높이가 너무 크면 하단 폭 증가
        H = p.total_height()
        if H > 10:
            p.avg_bottom_width = max(p.avg_bottom_width, H * 0.15)
            p.base_slab_width = max(p.base_slab_width, p.avg_bottom_width * 1.4)


def parse_retaining_wall(paths: list[str]) -> RetainingWallParams:
    return RetainingWallParser().parse(paths)


if __name__ == "__main__":
    import sys
    paths = sys.argv[1:] if len(sys.argv) > 1 else [
        "SAMPLE_CAD/1. 좌안옹벽 일반도 작성(2026.0109).dxf",
    ]
    p = parse_retaining_wall(paths)
    print(p.summary())
