"""옹벽 3D 파라메트릭 빌더.

구성요소:
1. 본체 (사다리꼴 단면을 길이방향으로 sweep)
2. 기초 slab (하부 넓은 base)
3. 뒤채움 지형 (배면 토사)
4. 배면 앵커바 × N (격자 배치)
5. 상단 안전난간 (parapet)
6. 수축이음 세로선 (표면에 시각화)
7. 배수공 (weep hole)
8. 전면 지반 + 바위
"""

from __future__ import annotations

import math

import numpy as np
import pyvista as pv

from retaining_wall_parser import RetainingWallParams


COLORS = {
    "wall":         "#A8A59B",    # 콘크리트
    "wall_face":    "#B5B2A7",    # 전면 (약간 밝게)
    "base":         "#8D8A80",    # 기초 slab
    "backfill":     "#8B7355",    # 뒤채움 토사
    "anchor":       "#2C3E50",    # 앵커바 (철)
    "anchor_plate": "#566573",    # 앵커 판
    "parapet":      "#A8A59B",
    "rail":         "#4A4A4A",    # 난간
    "joint":        "#5D4A33",    # 수축이음 (진한 선)
    "weep":         "#2C3E50",
    "ground":       "#8B7D6B",
    "rock":         "#6B5D50",    # 기초암반
}


class RetainingWallBuilder:

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

    def build_all(self):
        self.meshes = []

        self._build_base_slab()
        self._build_wall_body()
        self._build_backfill()
        self._build_anchors()
        self._build_parapet()
        self._build_contraction_joints()
        self._build_weep_holes()
        self._build_ground()

        return self.meshes

    # 좌표계:
    # X: 길이방향 (총 연장)
    # Y: 전면(-Y) ↔ 배면(+Y) 방향
    # Z: 높이 (EL)

    # --- 기초 slab (바닥) ---

    def _build_base_slab(self):
        p = self.p
        L = p.total_length
        W = p.base_slab_width
        T = p.base_slab_thickness
        z0 = p.bottom_el
        z1 = z0 + T

        # 기초는 벽 하단에서 전면/배면 모두 확장
        # 중심이 벽 중심과 같다고 가정
        self._add_box(-L/2, L/2, -W/2, W/2, z0, z1, COLORS["base"])

    # --- 본체 벽 (사다리꼴 단면 sweep) ---

    def _build_wall_body(self):
        p = self.p
        L = p.total_length
        z_bot = p.bottom_el + p.base_slab_thickness
        z_top = p.top_el

        W_bot = p.avg_bottom_width
        W_top = p.avg_top_width

        # 전면 경사: 하단이 더 앞으로 나온 사다리꼴
        # 단면: YZ 평면에서
        #   하단: (-W_bot/2, 0) ~ (W_bot/2, 0)
        #   상단: (-W_top/2, H) ~ (W_top/2, H)
        # 실제로는 배면은 수직, 전면만 경사

        # 배면 벽 (Y+ 면)
        # 단면:
        #   전면(Y-): (-W_bot/2 + batter*H, z_bot) → (-W_top/2, z_top)
        #   배면(Y+): (W_bot/2, z_bot) → (W_bot/2, z_top)  (수직)
        # batter: 전면이 안쪽으로 기움 (현재는 W_top - W_bot 차이로 묵시적 처리)

        # 8개 코너 점
        y_front_bot = -W_bot / 2
        y_front_top = -W_top / 2  # 전면은 상단에서 얇아짐
        y_back_bot = W_bot / 2
        y_back_top = W_top / 2    # 배면도 상단에서 얇아짐 (대칭 사다리꼴) 아니면 수직

        # 실제 옹벽은 배면 수직이 더 흔함. 수직으로 고정:
        y_back_bot = W_bot / 2
        y_back_top = W_bot / 2

        pts = np.array([
            [-L/2, y_front_bot, z_bot],  # 0 좌하전
            [L/2,  y_front_bot, z_bot],  # 1 우하전
            [L/2,  y_back_bot,  z_bot],  # 2 우하배
            [-L/2, y_back_bot,  z_bot],  # 3 좌하배
            [-L/2, y_front_top, z_top],  # 4 좌상전
            [L/2,  y_front_top, z_top],  # 5 우상전
            [L/2,  y_back_top,  z_top],  # 6 우상배
            [-L/2, y_back_top,  z_top],  # 7 좌상배
        ])
        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), COLORS["wall"], 1.0))

    # --- 뒤채움 (배면 토사) ---

    def _build_backfill(self):
        p = self.p
        L = p.total_length
        z_top = p.top_el

        # 배면에서 뒤로 뻗은 토사 (30 길이)
        back_depth = 15.0
        y_start = p.avg_bottom_width / 2  # 벽 배면
        y_end = y_start + back_depth

        # 토사는 상단부터 아래로 경사 (자연 지형)
        # 단면: 상단 수평, 경사면, 바닥 수평
        pts = np.array([
            [-L/2, y_start, z_top],
            [ L/2, y_start, z_top],
            [ L/2, y_end,   z_top],  # 뒤쪽 상단
            [-L/2, y_end,   z_top],
            # 바닥은 z_top - 1 정도로 살짝 낮게
            [-L/2, y_start, z_top - 0.1],
            [ L/2, y_start, z_top - 0.1],
            [ L/2, y_end,   z_top - 3],
            [-L/2, y_end,   z_top - 3],
        ])
        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), COLORS["backfill"], 1.0))

    # --- 배면 앵커바 (격자 배치) ---

    def _build_anchors(self):
        p = self.p
        if not p.has_anchors:
            return

        L = p.total_length
        z_bot = p.bottom_el + p.base_slab_thickness
        z_top = p.top_el - 1.0  # 상단은 난간 영역 제외

        # 격자 개수 결정
        dx = p.anchor_spacing_h
        dz = p.anchor_spacing_v
        nx = max(int(L / dx), 2)
        nz = max(int((z_top - z_bot) / dz), 2)

        # 개수 상한 (너무 많으면 안 만듦)
        max_total = 200
        if nx * nz > max_total:
            # 간격 늘리기
            ratio = math.sqrt(nx * nz / max_total)
            nx = int(nx / ratio)
            nz = int(nz / ratio)

        # 격자 배치
        y_wall_back = p.avg_bottom_width / 2  # 벽 배면 Y 좌표
        angle_rad = math.radians(p.anchor_angle_deg)
        anchor_L = p.anchor_length
        # 앵커는 배면에서 아래쪽으로 경사져 안으로 매입
        dx_anchor = math.cos(angle_rad) * anchor_L
        dz_anchor = -math.sin(angle_rad) * anchor_L

        for i in range(nx):
            for j in range(nz):
                x = -L/2 + (i + 0.5) * (L / nx)
                z = z_bot + (j + 0.5) * ((z_top - z_bot) / nz)

                start = np.array([x, y_wall_back, z])
                end = np.array([x, y_wall_back + dx_anchor, z + dz_anchor])

                # 앵커바 (얇은 실린더)
                try:
                    length = float(np.linalg.norm(end - start))
                    direction = (end - start) / length
                    anchor = pv.Cylinder(
                        center=tuple((start + end) / 2),
                        direction=tuple(direction),
                        radius=p.anchor_diameter / 2,
                        height=length,
                        resolution=8,
                    ).extract_surface()
                    self.meshes.append((anchor, COLORS["anchor"], 1.0))
                except Exception:
                    continue

                # 앵커 헤드 판 (벽 면에 붙은 작은 사각)
                plate_size = 0.2
                self._add_box(
                    x - plate_size / 2, x + plate_size / 2,
                    y_wall_back, y_wall_back + 0.05,
                    z - plate_size / 2, z + plate_size / 2,
                    COLORS["anchor_plate"],
                )

    # --- 상단 파라펫 / 난간 ---

    def _build_parapet(self):
        p = self.p
        if not p.has_parapet:
            return

        L = p.total_length
        z0 = p.top_el
        z1 = z0 + p.parapet_height
        t = p.parapet_thickness

        # 전면 파라펫
        y_front = -p.avg_top_width / 2
        self._add_box(-L/2, L/2, y_front, y_front + t, z0, z1, COLORS["parapet"])

        # 배면 파라펫 (선택적)
        y_back = p.avg_top_width / 2  # 상단 폭 기준
        self._add_box(-L/2, L/2, y_back - t, y_back, z0, z1, COLORS["parapet"])

        # 난간 (수평 바)
        rail_t = 0.08
        for rz in [z0 + p.parapet_height * 0.7, z0 + p.parapet_height * 0.4]:
            self._add_box(-L/2, L/2, y_front, y_front + rail_t, rz, rz + rail_t, COLORS["rail"])

    # --- 수축이음 (표면 세로선) ---

    def _build_contraction_joints(self):
        p = self.p
        if not p.has_contraction_joints:
            return

        L = p.total_length
        z_bot = p.bottom_el + p.base_slab_thickness
        z_top = p.top_el
        spacing = p.joint_spacing
        n = max(int(L / spacing), 1)

        # 전면(Y-)에 얇은 세로 띠 (시각적 이음)
        joint_width = 0.05
        y_front = -p.avg_bottom_width / 2 - 0.01  # 전면 살짝 앞
        depth = 0.08

        for i in range(1, n):
            x = -L/2 + i * (L / n)
            self._add_box(
                x - joint_width / 2, x + joint_width / 2,
                y_front, y_front + depth,
                z_bot, z_top,
                COLORS["joint"],
            )

    # --- 배수공 (전면 작은 구멍) ---

    def _build_weep_holes(self):
        p = self.p
        if not p.has_weep_holes:
            return

        L = p.total_length
        z_row = p.bottom_el + p.base_slab_thickness + 1.0  # 바닥 약간 위 한 줄
        spacing = p.weep_hole_spacing
        n = max(int(L / spacing), 1)

        y_front = -p.avg_bottom_width / 2 - 0.05
        r = p.weep_hole_diameter / 2

        for i in range(n):
            x = -L/2 + (i + 0.5) * (L / n)
            try:
                hole = pv.Cylinder(
                    center=(x, y_front, z_row),
                    direction=(0, 1, 0),
                    radius=r,
                    height=0.2,
                    resolution=12,
                ).extract_surface()
                self.meshes.append((hole, COLORS["weep"], 1.0))
            except Exception:
                continue

    # --- 전면 지반 ---

    def _build_ground(self):
        p = self.p
        L = p.total_length
        # 전면 지반 (앞쪽으로 15m)
        y_start = -p.avg_bottom_width / 2 - 15
        y_end = -p.avg_bottom_width / 2
        z_ground = p.ground_level
        pts = np.array([
            [-L/2 - 5, y_start, z_ground],
            [L/2 + 5,  y_start, z_ground],
            [L/2 + 5,  y_end,   z_ground],
            [-L/2 - 5, y_end,   z_ground],
        ])
        self.meshes.append((pv.PolyData(pts, np.array([4, 0, 1, 2, 3])),
                            COLORS["ground"], 1.0))

        # 기초암반 (벽 아래에서 전면으로 노출)
        rock_depth = 3.0
        rock_y_start = -p.avg_bottom_width / 2 - 3
        rock_y_end = -p.avg_bottom_width / 2 - 0.5
        z_rock_top = p.bottom_el
        z_rock_bot = p.bottom_el - rock_depth

        self._add_box(
            -L/2 - 3, L/2 + 3,
            rock_y_start, rock_y_end,
            z_rock_bot, z_rock_top,
            COLORS["rock"],
        )

    # --- 헬퍼 ---

    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 build_retaining_wall_meshes(params: RetainingWallParams):
    return RetainingWallBuilder(params).build_all()


if __name__ == "__main__":
    from retaining_wall_parser import parse_retaining_wall
    paths = ["SAMPLE_CAD/1. 좌안옹벽 일반도 작성(2026.0109).dxf"]
    p = parse_retaining_wall(paths)
    print(p.summary())
    meshes = RetainingWallBuilder(p).build_all()
    print(f"\n{len(meshes)}개 구성요소 생성")
