"""제수변실 + 도수관로 3D 파라메트릭 빌더.

구성요소:
1. 실 본체 (콘크리트 박스)
2. 벽체 절개 뷰 (내부가 보이도록 상부 일부 제거)
3. 도수관 (chamber 관통 파이프)
4. 송수관 (외부 연장)
5. 밸브 × N (원통 + 핸들)
6. 상단 슬라이드 뚜껑 / 맨홀
7. 내부 바닥 slabs (각 EL)
8. 외부 출입 계단
9. 지반
"""

from __future__ import annotations


import numpy as np
import pyvista as pv

from valve_chamber_parser import ValveChamberParams, Valve


COLORS = {
    "chamber":    "#A8A59B",
    "slab":       "#9A968C",
    "pipe_steel": "#4E6E8E",   # 강재도관 (강청색)
    "pipe_cast":  "#6B7B8C",   # 주철관
    "valve_body": "#27AE60",   # 밸브 바디 (녹색 - 산업 표준)
    "valve_handle":"#E74C3C",  # 밸브 핸들 (빨강)
    "valve_motor":"#F39C12",   # 전동 모터 (주황)
    "hatch":      "#BDC3C7",   # 뚜껑
    "stairs":     "#8B7D6B",
    "parapet":    "#A8A59B",
    "ground":     "#7F6F5F",
    "pipe_conn":  "#4A4A4A",   # 플랜지
}


class ValveChamberBuilder:
    def __init__(self, params: ValveChamberParams):
        self.p = params
        self.meshes: list[tuple[pv.PolyData, str, float]] = []

    def build_all(self):
        self.meshes = []
        self._build_chamber_body()
        self._build_floor_slabs()
        self._build_main_conduit()
        self._build_transmission_pipes()
        self._build_valves()
        self._build_hatch()
        self._build_entry_stairs()
        self._build_ground()
        return self.meshes

    # --- 실 본체 ---

    def _build_chamber_body(self):
        p = self.p
        hw = p.chamber_width / 2
        hd = p.chamber_depth / 2
        wt = p.chamber_wall_thickness
        z0 = p.bottom_el
        z_top = p.top_el

        # 4개 벽 (상단 일부는 뚜껑을 위해 빼지 않음)
        # 앞 (Y = -hd)
        self._add_box(-hw, hw, -hd, -hd + wt, z0, z_top, COLORS["chamber"])
        # 뒤
        self._add_box(-hw, hw, hd - wt, hd, z0, z_top, COLORS["chamber"])
        # 좌
        self._add_box(-hw, -hw + wt, -hd + wt, hd - wt, z0, z_top, COLORS["chamber"])
        # 우
        self._add_box(hw - wt, hw, -hd + wt, hd - wt, z0, z_top, COLORS["chamber"])

        # 바닥
        self._add_box(-hw, hw, -hd, hd, z0, z0 + 0.4, COLORS["chamber"])

        # 상판 (지붕) - 뚜껑 공간 제외
        slab_t = 0.4
        if p.has_hatch and p.hatch_count > 0:
            # 뚜껑 위치는 상단 중앙
            hatch_s = p.hatch_size / 2
            n = p.hatch_count
            # 뚜껑 여러 개면 X방향 분산
            for i in range(n):
                hx = 0 if n == 1 else -hw * 0.5 + i / (n - 1) * hw

                # 상판은 뚜껑 기준 좌우로 분할
                # 좌측
                if i == 0:
                    self._add_box(-hw, hx - hatch_s, -hd, hd, z_top, z_top + slab_t, COLORS["chamber"])
                # 우측 (마지막이면 우측 끝까지)
                if i == n - 1:
                    self._add_box(hx + hatch_s, hw, -hd, hd, z_top, z_top + slab_t, COLORS["chamber"])
                # 뚜껑 주변 Y 방향 채움
                self._add_box(hx - hatch_s, hx + hatch_s, -hd, -hatch_s, z_top, z_top + slab_t, COLORS["chamber"])
                self._add_box(hx - hatch_s, hx + hatch_s, hatch_s, hd, z_top, z_top + slab_t, COLORS["chamber"])
        else:
            # 풀 상판
            self._add_box(-hw, hw, -hd, hd, z_top, z_top + slab_t, COLORS["chamber"])

    # --- 내부 바닥 slab (각 EL) ---

    def _build_floor_slabs(self):
        p = self.p
        hw = p.chamber_width / 2 - p.chamber_wall_thickness - 0.1
        hd = p.chamber_depth / 2 - p.chamber_wall_thickness - 0.1
        for el in p.floor_elevations:
            if el <= p.bottom_el + 0.4:
                continue
            if el >= p.top_el - 0.4:
                continue
            self._add_box(-hw, hw, -hd, hd, el - 0.1, el + 0.1, COLORS["slab"])

    # --- 도수관 (chamber 관통) ---

    def _build_main_conduit(self):
        """파서가 M-301/도수관 pipe를 추출했으면 그쪽에 위임 (no-op).

        분기(has_inlet_branch)·단일 모두 파서 `_finalize`가 pipe 객체로 생성하므로
        빌더는 `_build_transmission_pipes` 한 경로만 돌면 된다. 파서에서 도수관이
        전혀 없을 때만(비정상 상태) 경고 출력 후 종료.
        """
        p = self.p
        has_main = any(
            ("M-301" in pp.name) or ("도수관" in pp.name)
            for pp in (p.pipes or [])
        )
        if has_main:
            return
        # 여기에 도달한다면 파서가 도수관을 생성 실패한 것 — 조용히 skip하여
        # "혼자 떠 있는 고아 파이프"가 생기지 않도록 한다.
        return

    # --- 송수관 및 외부 연장 ---

    def _build_transmission_pipes(self):
        """parser가 추출한 모든 pipe를 명시적 start/end/diameter로 빌드."""
        p = self.p
        if not p.pipes:
            return
        for pipe in p.pipes:
            start = pipe.start
            end = pipe.end
            # 유효성 체크: zero-vector 이면 폴백 _finalize에서 처리됨
            if start == (0.0, 0.0, 0.0) and end == (0.0, 0.0, 0.0):
                continue
            is_main = ("M-301" in pipe.name) or ("도수관" in pipe.name)
            color = COLORS["pipe_steel"] if is_main else COLORS["pipe_cast"]
            self._add_pipe(start, end, pipe.diameter, color)
            # 양 끝 플랜지 (도수관/대구경만)
            if pipe.diameter >= 0.3:
                self._add_flange(start, pipe.diameter, COLORS["pipe_conn"])
                self._add_flange(end, pipe.diameter, COLORS["pipe_conn"])

    # --- 밸브 ---

    def _build_valves(self):
        p = self.p
        for v in p.valves:
            self._build_single_valve(v)

    def _build_single_valve(self, v: Valve):
        """밸브 하나: 바디 + 핸들/모터."""
        # 위치 검증
        z = v.elevation
        x = v.center_x
        y = v.center_y

        # 밸브 바디 (축정렬 원통 또는 박스)
        body_r = v.diameter / 2
        body_h = v.diameter * 1.5

        # 밸브가 놓인 파이프 방향 추정 (X 또는 Y)
        # 가장 가까운 pipe의 (end-start) 방향을 사용; 없으면 X축 기본
        flow_dir = (1.0, 0.0, 0.0)
        try:
            best_d = None
            for pp in (self.p.pipes or []):
                if pp.start == (0,0,0) and pp.end == (0,0,0):
                    continue
                # pipe 중간점과 valve 거리
                mx = (pp.start[0] + pp.end[0]) / 2
                my = (pp.start[1] + pp.end[1]) / 2
                d = ((mx - x)**2 + (my - y)**2) ** 0.5
                if best_d is None or d < best_d:
                    dx = pp.end[0] - pp.start[0]
                    dy = pp.end[1] - pp.start[1]
                    L = (dx*dx + dy*dy) ** 0.5
                    if L > 1e-6:
                        flow_dir = (dx/L, dy/L, 0.0)
                        best_d = d
        except Exception:
            pass

        if v.valve_type == "BUTTERFLY":
            # 버터플라이: 납작한 원통 (관로 방향에 직교 평면)
            body = pv.Cylinder(
                center=(x, y, z),
                direction=flow_dir,
                radius=body_r * 1.1,
                height=body_h * 0.6,
                resolution=20,
            ).extract_surface()
            self.meshes.append((body, COLORS["valve_body"], 1.0))

            # 상부 모터 박스
            motor_size = body_r * 1.2
            motor_base = z + body_r * 1.1
            motor_top = motor_base + motor_size * 2
            self._add_box(
                x - motor_size / 2, x + motor_size / 2,
                y - motor_size / 2, y + motor_size / 2,
                motor_base, motor_top,
                COLORS["valve_motor"],
            )

            # 모터 축
            stem = pv.Cylinder(
                center=(x, y, (z + body_r + motor_base) / 2),
                direction=(0, 0, 1),
                radius=0.05,
                height=(motor_base - z - body_r),
                resolution=8,
            ).extract_surface()
            self.meshes.append((stem, COLORS["pipe_conn"], 1.0))

        elif v.valve_type == "GATE":
            # 게이트: 박스형 바디. 관로 방향이 X면 X축으로 길게, Y면 Y축으로 길게
            if abs(flow_dir[0]) >= abs(flow_dir[1]):
                # X 방향 흐름 → 박스도 X 길이↑
                self._add_box(
                    x - body_r * 1.4, x + body_r * 1.4,
                    y - body_r, y + body_r,
                    z - body_r, z + body_r,
                    COLORS["valve_body"],
                )
            else:
                # Y 방향 흐름
                self._add_box(
                    x - body_r, x + body_r,
                    y - body_r * 1.4, y + body_r * 1.4,
                    z - body_r, z + body_r,
                    COLORS["valve_body"],
                )

            # 상부 stem (게이트 밸브의 특징)
            stem_h = body_r * 4
            stem = pv.Cylinder(
                center=(x, y, z + body_r + stem_h / 2),
                direction=(0, 0, 1),
                radius=0.04,
                height=stem_h,
                resolution=8,
            ).extract_surface()
            self.meshes.append((stem, COLORS["pipe_conn"], 1.0))

            # 상부 핸들 or 모터
            handle_r = body_r * 1.0
            handle = pv.Cylinder(
                center=(x, y, z + body_r + stem_h),
                direction=(0, 0, 1),
                radius=handle_r,
                height=0.1,
                resolution=16,
            ).extract_surface()
            self.meshes.append((handle, COLORS["valve_handle"], 1.0))

        else:
            # 일반: 단순 박스
            self._add_box(
                x - body_r, x + body_r,
                y - body_r, y + body_r,
                z - body_r, z + body_r,
                COLORS["valve_body"],
            )

    # --- 상단 슬라이드 뚜껑 ---

    def _build_hatch(self):
        p = self.p
        if not p.has_hatch:
            return
        hw = p.chamber_width / 2
        hs = p.hatch_size / 2
        z = p.top_el + 0.5  # 상판 위

        for i in range(p.hatch_count):
            hx = 0 if p.hatch_count == 1 else -hw * 0.5 + i / (p.hatch_count - 1) * hw
            # 뚜껑 (약간 돌출)
            self._add_box(
                hx - hs, hx + hs,
                -hs, hs,
                z, z + 0.15,
                COLORS["hatch"],
            )

    # --- 외부 출입 계단 ---

    def _build_entry_stairs(self):
        p = self.p
        if not p.has_entry_stairs:
            return
        hw = p.chamber_width / 2
        total_rise = p.top_el - p.bottom_el
        n_steps = max(int(total_rise / 0.17), 8)
        step_h = total_rise / n_steps
        step_d = 0.28
        stair_w = 1.2
        z0 = p.bottom_el

        # 좌측 외부 계단
        x_start = -hw - 0.5
        for i in range(n_steps):
            sx0 = x_start - (i + 1) * step_d
            sx1 = x_start - i * step_d
            z_top = z0 + (i + 1) * step_h
            self._add_box(sx0, sx1,
                          -stair_w / 2, stair_w / 2,
                          z0, z_top,
                          COLORS["stairs"])

    # --- 지반 ---

    def _build_ground(self):
        p = self.p
        margin = max(p.chamber_width, p.chamber_depth) * 0.8
        hw = p.chamber_width / 2 + margin
        hd = p.chamber_depth / 2 + margin
        ground_el = p.bottom_el - 0.5
        pts = np.array([
            [-hw, -hd, ground_el], [hw, -hd, ground_el],
            [hw, hd, ground_el], [-hw, hd, ground_el],
        ])
        self.meshes.append((pv.PolyData(pts, np.array([4, 0, 1, 2, 3])),
                            COLORS["ground"], 1.0))

    # --- 저수준 헬퍼 ---

    def _add_box(self, x0, x1, y0, y1, z0, z1, color):
        if x1 <= x0 or y1 <= y0 or z1 <= z0:
            return
        pts = np.array([
            [x0, y0, z0], [x1, y0, z0], [x1, y1, z0], [x0, y1, z0],
            [x0, y0, z1], [x1, y0, z1], [x1, y1, z1], [x0, y1, z1],
        ])
        faces = np.hstack([
            [4, 0, 3, 2, 1], [4, 4, 5, 6, 7],
            [4, 0, 1, 5, 4], [4, 2, 3, 7, 6],
            [4, 1, 2, 6, 5], [4, 0, 4, 7, 3],
        ])
        self.meshes.append((pv.PolyData(pts, faces), color, 1.0))

    def _add_pipe(self, start, end, diameter, color):
        try:
            s = np.array(start)
            e = np.array(end)
            length = float(np.linalg.norm(e - s))
            if length < 0.1:
                return
            direction = (e - s) / length
            center = (s + e) / 2
            pipe = pv.Cylinder(
                center=tuple(center),
                direction=tuple(direction),
                radius=diameter / 2,
                height=length,
                resolution=20,
            ).extract_surface()
            self.meshes.append((pipe, color, 1.0))
        except Exception:
            pass

    def _add_flange(self, pos, diameter, color):
        try:
            flange = pv.Cylinder(
                center=pos,
                direction=(1, 0, 0),  # X방향 간단 배치
                radius=diameter / 2 * 1.3,
                height=0.15,
                resolution=16,
            ).extract_surface()
            self.meshes.append((flange, color, 1.0))
        except Exception:
            pass


def build_valve_chamber_meshes(params: ValveChamberParams):
    return ValveChamberBuilder(params).build_all()


if __name__ == "__main__":
    from valve_chamber_parser import parse_valve_chamber
    paths = ["SAMPLE_CAD/12996710-M43-002 신설 제수변실 설비 배치도.dxf"]
    p = parse_valve_chamber(paths)
    print(p.summary())
    meshes = ValveChamberBuilder(p).build_all()
    print(f"\n{len(meshes)}개 구성요소 생성")
