"""취수탑 (Intake Tower) 전용 DXF 파서.

취수탑 구조의 특성:
- L자 또는 직사각 콘크리트 본체 (여러 층 구조)
- 다수의 취수수문 (각각 다른 EL에 배치)
- 수문마다 개폐장치 (원통형)
- 상부 호이스트 크레인 + 레일
- 점검구, 계단, 사다리
- 여러 바닥 slab (각 EL별)

핵심 파싱 로직:
1. 뷰 검출: 평면도 / 정면도 / 측면도
2. 수문 위치: 정면도 내 반복되는 원(개폐장치 상징) → 개수 + 위치 + EL
3. 본체 외곽: 정면도 or 평면도의 최대 closed polygon
4. 주요 EL: 텍스트 "EL.XXX.XXX" 패턴
5. 호이스트 레일: 상단 긴 수평 LINE
6. 지붕 / 바닥 slabs: 여러 EL 별 수평선

사용법:
    parser = IntakeTowerParser()
    params = parser.parse([plan_section_dxf_path])
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field

import ezdxf

from view_detector import detect_view_regions, ViewRegion
from dxf_geometry import extract_structural_geometry


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

@dataclass
class GatePosition:
    """개별 취수수문 정보."""
    index: int                    # 0부터
    center_x: float               # 본체 로컬 X (m)
    elevation: float              # EL (m, 해발)
    actuator_radius: float = 0.6  # 개폐장치 원통 반경
    gate_width: float = 2.0       # 수문 폭 (로컬 X방향)
    gate_height: float = 2.0      # 수문 높이
    label: str = ""


@dataclass
class IntakeTowerParams:
    """취수탑 파라미터 (단위: m)."""

    # 본체 외곽
    body_width: float = 11.2         # 가로
    body_depth: float = 6.4          # 세로 (평면도에서)
    body_bottom_el: float = 39.0     # 바닥 EL
    body_top_el: float = 57.2        # 상단 EL

    # L자 여부 (접근수로 옹벽 포함)
    has_l_extension: bool = True     # 한쪽으로 연장된 부분
    extension_length: float = 14.5   # 연장 길이
    extension_width: float = 6.4     # 연장 폭
    extension_bottom_el: float = 41.0

    # 수문 배치 (정면도 기준)
    gates: list = field(default_factory=list)

    # 호이스트
    has_hoist: bool = True
    hoist_rail_el: float = 56.0      # 호이스트 레일 EL
    hoist_rail_length: float = 10.0

    # 지붕
    roof_type: str = "flat"          # flat | gabled
    roof_thickness: float = 0.5

    # 내부 바닥 slabs (각 EL)
    floor_elevations: list = field(default_factory=list)  # [43.0, 46.0, 48.5, ...]

    # 외부 출입
    has_entry_stairs: bool = True
    stairs_width: float = 1.5
    stairs_side: str = "left"        # left | right | front | back

    # 점검구
    has_inspection_cover: bool = True
    inspection_cover_x: float = 2.0   # 본체 로컬 X
    inspection_cover_y: float = 3.0
    inspection_cover_size: float = 2.5

    # 난간
    has_parapet: bool = True
    parapet_height: float = 1.1

    # 소스 파일
    source_files: list = field(default_factory=list)
    raw_annotations: list = field(default_factory=list)

    def summary(self) -> str:
        return (
            f"Intake Tower: {self.body_width:.1f} × {self.body_depth:.1f}m, "
            f"EL.{self.body_bottom_el:.1f}~{self.body_top_el:.1f} "
            f"(H={self.body_top_el - self.body_bottom_el:.1f}m)\n"
            f"  수문 {len(self.gates)}개, 바닥 {len(self.floor_elevations)}개 EL, "
            f"호이스트 {'O' if self.has_hoist else 'X'}"
        )


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

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


class IntakeTowerParser:
    """취수탑 DXF 파서."""

    def parse(self, dxf_paths: list[str]) -> IntakeTowerParams:
        """여러 DXF 파일에서 파라미터 추출."""
        params = IntakeTowerParams()
        params.source_files = list(dxf_paths)

        # 모든 DXF를 순회하며 정보 수집
        for path in dxf_paths:
            try:
                self._parse_single(path, params)
            except Exception as e:
                print(f"  파싱 오류 ({path}): {e}")

        # 정리 및 정규화
        self._finalize_params(params)
        return params

    def _parse_single(self, path: str, params: IntakeTowerParams):
        """단일 DXF에서 정보 추출 → params에 누적."""
        doc = ezdxf.readfile(path)
        msp = doc.modelspace()

        geom = extract_structural_geometry(path)
        scale = geom.unit_scale

        views = detect_view_regions(path)

        # 1) 표고(EL) 텍스트 수집
        el_texts = self._collect_el_texts(msp, scale)
        params.raw_annotations.extend(
            [(f"EL.{v:.2f}", x, y) for (x, y, v) in el_texts]
        )

        if el_texts:
            els = [v for (_, _, v) in el_texts]
            params.body_top_el = max(params.body_top_el, *els)
            params.body_bottom_el = min(params.body_bottom_el, *els)

        # 2) 수문 개폐장치 원 검출 (정면도 내)
        front_view = self._find_view(views, "front")
        if front_view:
            gates = self._detect_gates_in_front_view(msp, front_view, el_texts, scale)
            if gates:
                params.gates = gates

        # 3) 평면도 영역에서 본체 크기 추정
        plan_view = self._find_view(views, "plan")
        if plan_view:
            # 평면도 bbox를 본체 크기로 (근사)
            params.body_width = max(params.body_width, plan_view.width)
            params.body_depth = max(params.body_depth, plan_view.height)

        # 4) 호이스트 레일 검출 (상단 긴 수평선)
        hoist = self._detect_hoist_rail(msp, scale, params.body_top_el)
        if hoist:
            params.hoist_rail_el = hoist["el"]
            params.hoist_rail_length = hoist["length"]
            params.has_hoist = True

        # 5) 바닥 EL 목록 (표고 텍스트 + 수문 EL)
        floor_els = set()
        for (_, _, v) in el_texts:
            if v > params.body_bottom_el + 0.5 and v < params.body_top_el - 0.5:
                floor_els.add(round(v, 1))
        for g in params.gates:
            floor_els.add(round(g.elevation, 1))
        params.floor_elevations = sorted(floor_els)

    def _collect_el_texts(self, msp, scale: float) -> list[tuple]:
        """모든 EL. 텍스트 수집 → [(x, y, value), ...]."""
        results = []
        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
                    results.append((pos.x * scale, pos.y * scale, float(m.group(1))))
            except Exception:
                continue
        return results

    def _find_view(self, views: list[ViewRegion], view_type: str) -> ViewRegion | None:
        for v in views:
            if v.view_type == view_type:
                return v
        return None

    def _detect_gates_in_front_view(self, msp, front_view: ViewRegion,
                                     el_texts: list, scale: float) -> list[GatePosition]:
        """정면도 내 반복되는 원(개폐장치) → 수문 배치.

        반복 조건: 같은 반경의 원이 3개 이상, 같은 X 또는 Y선상 정렬.
        """
        # 정면도 bbox (월드 좌표, m)
        fx0, fy0, fx1, fy1 = front_view.bounds
        # margin 확대 (원이 bbox 경계 걸쳐 있을 수 있음)
        margin = 1.0
        fx0 -= margin; fy0 -= margin; fx1 += margin; fy1 += margin

        # 정면도 영역 안의 원들 수집
        circles_in_view = []
        for e in msp.query("CIRCLE"):
            try:
                cx = e.dxf.center.x * scale
                cy = e.dxf.center.y * scale
                r = e.dxf.radius * scale
                if fx0 <= cx <= fx1 and fy0 <= cy <= fy1:
                    # 너무 작은 원(볼트/리벳)은 제외
                    if r < 0.05:
                        continue
                    circles_in_view.append((cx, cy, r, e.dxf.layer))
            except Exception:
                continue

        if len(circles_in_view) < 2:
            return []

        # 반경별 그룹화 (0.1m 허용오차)
        from collections import defaultdict
        groups = defaultdict(list)
        for cx, cy, r, layer in circles_in_view:
            key = round(r, 1)
            groups[key].append((cx, cy, r, layer))

        # 3개 이상의 그룹 우선 (수문은 보통 3문), 2개도 허용
        candidate_groups = [g for g in groups.values() if len(g) >= 2]
        if not candidate_groups:
            return []

        # 가장 큰 반경의 그룹 선택 (수문 개폐장치는 보통 큼)
        candidate_groups.sort(key=lambda g: (-g[0][2], -len(g)))
        main_group = candidate_groups[0]

        # 수문 위치 확정: X 또는 Y 정렬 여부 확인 (현재는 정면도 가정 — 좌우/상하 배치 분기는 미구현)
        # X 변화가 크면 → 수문이 좌우 배치 (평면도), Y 변화가 크면 → 상하 배치 (정면도, EL별)
        gates = []
        # 로컬 X 좌표 계산 (정면도 내에서 중심 기준)
        front_cx = (front_view.bounds[0] + front_view.bounds[2]) / 2

        for i, (cx, cy, r, layer) in enumerate(sorted(main_group, key=lambda c: c[1])):
            # EL 추정: cy 좌표 근처의 EL 텍스트 찾기
            best_el = 43.0 + i * 2.5  # 기본값
            best_dist = 5.0
            for (ex, ey, ev) in el_texts:
                if abs(ey - cy) < best_dist:
                    best_dist = abs(ey - cy)
                    best_el = ev

            local_x = cx - front_cx
            gates.append(GatePosition(
                index=i,
                center_x=local_x,
                elevation=best_el,
                actuator_radius=r,
                gate_width=max(r * 3, 1.5),
                gate_height=max(r * 3, 1.5),
                label=f"수문{i+1} EL.{best_el:.2f}",
            ))

        return gates

    def _detect_hoist_rail(self, msp, scale: float, top_el: float) -> dict | None:
        """상단 긴 수평선 검출 → 호이스트 레일."""
        best = None
        for e in msp.query("LINE"):
            try:
                s = e.dxf.start
                en = e.dxf.end
                dx = abs(en.x - s.x) * scale
                dy = abs(en.y - s.y) * scale
                # 수평선 + 길이 5m 이상
                if dy < 0.3 and dx > 5.0:
                    y_el = s.y * scale
                    # 상단 1/3 영역만 (top_el 부근)
                    # y값의 절대 위치는 EL과 꼭 맞진 않음 → 도면 좌표계 기준으로 위쪽 1/3
                    if best is None or dx > best["length"]:
                        best = {"el": top_el - 1.5, "length": dx, "y_raw": y_el}
            except Exception:
                continue
        return best

    def _finalize_params(self, params: IntakeTowerParams):
        """파라미터 정리 및 기본값 보완."""
        # 바닥 EL은 수문 최저 EL 아래로 조정
        if params.gates:
            min_gate_el = min(g.elevation for g in params.gates)
            if params.body_bottom_el > min_gate_el - 1:
                params.body_bottom_el = min_gate_el - 4.0

        # 수문이 하나도 없으면 기본 3문 가정
        if not params.gates:
            for i in range(3):
                params.gates.append(GatePosition(
                    index=i,
                    center_x=(i - 1) * 3.0,
                    elevation=params.body_bottom_el + 4 + i * 2.5,
                    actuator_radius=0.6,
                    gate_width=2.0,
                    gate_height=2.0,
                ))

        # 호이스트 레일 EL이 상단과 불일치하면 상단 - 2m로 조정
        if (params.has_hoist
                and (params.hoist_rail_el > params.body_top_el
                     or params.hoist_rail_el < params.body_bottom_el)):
            params.hoist_rail_el = params.body_top_el - 2.0


# 편의 함수
def parse_intake_tower(dxf_paths: list[str]) -> IntakeTowerParams:
    return IntakeTowerParser().parse(dxf_paths)


if __name__ == "__main__":
    import sys

    paths = sys.argv[1:] if len(sys.argv) > 1 else [
        "SAMPLE_CAD/12996710-M40-001 신설 취수탑 설비 설치도(1／2).dxf",
        "SAMPLE_CAD/12996710-M40-002 신설 취수탑 설비 설치도(2／2).dxf",
    ]

    params = parse_intake_tower(paths)
    print(params.summary())
    print()
    print("상세 수문 정보:")
    for g in params.gates:
        print(f"  {g.label} @ X={g.center_x:+.1f}m, R={g.actuator_radius:.2f}m")
    print(f"\n바닥 EL 목록: {params.floor_elevations}")
    if params.has_hoist:
        print(f"호이스트 레일: EL.{params.hoist_rail_el:.1f}, 길이 {params.hoist_rail_length:.1f}m")
