"""
===============================================================================
PhD Research Pipeline: Coupled Plan-to-Facade Generative Optimization for
Microclimatic Comfort in Tropical Tube Houses & Vietnamese Modernist Heritage
===============================================================================
Tích hợp 2 chế độ:
  1. Coupled Tube-House Mode (Da Nang 2.5D Graph [8x6] + Tropical Facade [1x14] + Site [1x8] -> Perf [1x4] + POD [1x16])
  2. Heritage Facade Mode (Param_Vec [1x16] + Climate [1x6] -> Perf [1x3] + Depth_Grid [3x64x64])

Kế thừa thuật toán từ các kho mã nguồn mở trong `research-repos/`:
  - House-GAN++ (`ennauata/houseganpp`): Convolutional Message Passing (CMP) trên đồ thị không gian
  - Smarchitect (`Salar24/Smarchitect`): Bộ lọc thỏa mãn ràng buộc hình học (CSP Filter)
  - CFD-surrogate-models (`LigeZhao/CFD-surrogate-models`): Phân rã trực giao (POD-ANN) tái tạo trường gió
  - ThermalGen (`arplaboratory/ThermalGen`): Bản đồ phân bố nhiệt và bức xạ vỏ bao che
  - onnx2fmu (`HyRES-FBK/onnx2fmu`): Đóng gói mô hình ONNX sang Functional Mock-up Unit (FMU) cho EnergyPlus
===============================================================================
"""

import torch
import torch.nn as nn
import torch.nn.functional as F


# ==============================================================================
# 1. LỚP TRUYỀN THÔNG ĐIỆP ĐỒ THỊ 2.5D (KẾ THỪA TỪ HOUSE-GAN++)
# ==============================================================================
class SpatialGraphConv(nn.Module):
    """
    Truyền thông điệp giữa 8 nút không gian nhà ống 2.5D:
    0: Lô-gia mặt tiền, 1: Phòng khách T1, 2: Lồng cầu thang, 3: Giếng trời giữa,
    4: Bếp/Ăn T1, 5: Khối phòng trước T2-Tn, 6: Khối phòng sau T2-Tn, 7: Khe thoáng sau.
    """
    def __init__(self, in_features: int, out_features: int):
        super().__init__()
        self.proj = nn.Linear(in_features * 2, out_features)
        self.norm = nn.LayerNorm(out_features)

    def forward(self, node_feats: torch.Tensor, adj_matrix: torch.Tensor) -> torch.Tensor:
        deg = adj_matrix.sum(dim=-1, keepdim=True).clamp(min=1.0)
        neighbor_feats = torch.bmm(adj_matrix, node_feats) / deg
        combined = torch.cat([node_feats, neighbor_feats], dim=-1)
        return F.silu(self.norm(self.proj(combined)))


# ==============================================================================
# 2. MÔ HÌNH SURROGATE ĐA NHIỆM (KẾ THỪA CFD-SURROGATE & THERMALGEN)
# ==============================================================================
class MultiTaskTropicalSurrogate(nn.Module):
    """
    Dự báo tức thời (< 2 ms):
      - 4 chỉ số vi khí hậu chuẩn hóa [0, 1]: ACH (Thông gió), sDA (Chiếu sáng),
        SHG (Bức xạ nhiệt mặt đứng), ATC (Giờ tiện nghi nhiệt thích ứng ASHRAE 55)
      - 16 hệ số cơ sở trực giao POD (Proper Orthogonal Decomposition) tái tạo trường vận tốc gió 2.5D
    """
    def __init__(self, num_nodes=8, node_dim=6, fac_dim=14, site_dim=8, pod_modes=16):
        super().__init__()
        self.gconv1 = SpatialGraphConv(node_dim, 32)
        self.gconv2 = SpatialGraphConv(32, 32)

        # Nhánh mã hóa mặt đứng Hiện đại nhiệt đới (lam, bông gió, lô-gia) và khí hậu Đà Nẵng
        self.env_encoder = nn.Sequential(
            nn.Linear(fac_dim + site_dim, 64),
            nn.SiLU(),
            nn.Linear(64, 64),
            nn.LayerNorm(64),
            nn.SiLU()
        )

        # Bộ hợp nhất đặc trưng Mặt bằng 2.5D + Mặt đứng Nhiệt đới
        self.fusion = nn.Sequential(
            nn.Linear(32 + 64, 128),
            nn.LayerNorm(128),
            nn.SiLU(),
            nn.Linear(128, 64),
            nn.SiLU()
        )

        self.perf_head = nn.Sequential(nn.Linear(64, 4), nn.Sigmoid())
        self.pod_head = nn.Linear(64, pod_modes)

    def forward(self, node_feats, adj_matrix, fac_params, site_cond):
        h_graph = self.gconv1(node_feats, adj_matrix)
        h_graph = self.gconv2(h_graph, adj_matrix)
        h_plan = h_graph.mean(dim=1)  # Global Graph Pooling

        h_env = self.env_encoder(torch.cat([fac_params, site_cond], dim=-1))
        h_joint = self.fusion(torch.cat([h_plan, h_env], dim=-1))
        return self.perf_head(h_joint), self.pod_head(h_joint)


# ==============================================================================
# 3. MẠNG TẠO SINH ĐỒNG TỐI ƯU MẶT BẰNG - MẶT ĐỨNG (COUPLED GRAPH-CVAE)
# ==============================================================================
class CoupledTubeHouseGenerator(nn.Module):
    """
    Sinh đồng thời cấu trúc Mặt bằng 2.5D (tọa độ/kích thước giếng trời, phòng, lồng thang)
    và Mặt đứng Nhiệt đới (độ sâu lô-gia, góc lam, mật độ gạch bông gió) từ mục tiêu vi khí hậu y*.
    """
    def __init__(self, num_nodes=8, node_dim=6, fac_dim=14, site_dim=8, perf_dim=4, z_dim=16):
        super().__init__()
        self.num_nodes = num_nodes
        self.node_dim = node_dim
        cond_dim = site_dim + perf_dim

        self.fc_init = nn.Sequential(
            nn.Linear(z_dim + cond_dim, 128),
            nn.SiLU(),
            nn.Linear(128, num_nodes * 32),
            nn.SiLU()
        )
        self.gconv_gen = SpatialGraphConv(32, 32)

        # Nhánh xuất Ma trận nút Mặt bằng 2.5D [B, 8, 6]
        self.plan_out = nn.Sequential(nn.Linear(32, node_dim), nn.Sigmoid())

        # Nhánh xuất Vector Mặt đứng Nhiệt đới [B, 14]
        self.fac_out = nn.Sequential(
            nn.Linear(32 + cond_dim, 64),
            nn.SiLU(),
            nn.Linear(64, fac_dim),
            nn.Sigmoid()
        )

    def forward(self, z, adj_matrix, site_cond, target_perf):
        cond = torch.cat([site_cond, target_perf], dim=-1)
        batch_size = z.size(0)
        h = self.fc_init(torch.cat([z, cond], dim=-1))
        h_nodes = h.view(batch_size, self.num_nodes, 32)
        h_nodes = self.gconv_gen(h_nodes, adj_matrix)

        gen_plan_nodes = self.plan_out(h_nodes)
        h_global = h_nodes.mean(dim=1)
        gen_facade = self.fac_out(torch.cat([h_global, cond], dim=-1))
        return gen_plan_nodes, gen_facade


# ==============================================================================
# 4. HÀM MẤT MÁT DẪN DẮT BỞI VẬT LÝ & BẢO TOÀN DIỆN TÍCH (PHYSICS-GUIDED LOSS)
# ==============================================================================
def coupled_physics_guided_loss(
    nodes_true, nodes_hat,
    fac_true, fac_hat,
    adj_matrix, site_cond, target_perf,
    frozen_surrogate,
    lambda_phys=2.5,
    lambda_area=1.8,
    eta_min=0.82
):
    loss_recon = F.mse_loss(nodes_hat, nodes_true) + F.mse_loss(fac_hat, fac_true)
    pred_perf, _ = frozen_surrogate(nodes_hat, adj_matrix, fac_hat, site_cond)
    loss_phys = F.mse_loss(pred_perf, target_perf)

    # Ràng buộc bảo toàn diện tích sàn sử dụng hữu ích (eta_usable >= 0.82)
    atrium_len_ratio = nodes_hat[:, 3, 1]
    atrium_wid_ratio = nodes_hat[:, 3, 2]
    rear_len_ratio = nodes_hat[:, 7, 1]
    loggia_ratio = fac_hat[:, 0] * 0.12
    void_ratio = atrium_len_ratio * atrium_wid_ratio + rear_len_ratio + loggia_ratio
    eta_usable = 1.0 - void_ratio
    loss_area = torch.mean(F.relu(eta_min - eta_usable) ** 2)

    total = loss_recon + lambda_phys * loss_phys + lambda_area * loss_area
    return total, {
        "recon": loss_recon.item(),
        "phys": loss_phys.item(),
        "area": loss_area.item(),
        "total": total.item()
    }


# ==============================================================================
# 5. XUẤT CẶP MÔ HÌNH CHUẨN ONNX (EXPORT HYBRID PIPELINE)
# ==============================================================================
def export_hybrid_pipeline(
    surrogate_path="hybrid_surrogate.onnx",
    generator_path="hybrid_generator.onnx"
):
    surrogate = MultiTaskTropicalSurrogate().eval()
    generator = CoupledTubeHouseGenerator().eval()

    b, n = 1, 8
    d_nodes = torch.randn(b, n, 6)
    d_adj = torch.eye(n).unsqueeze(0)
    d_fac = torch.randn(b, 14)
    d_site = torch.randn(b, 8)
    d_perf = torch.randn(b, 4)
    d_z = torch.randn(b, 16)

    torch.onnx.export(
        surrogate,
        (d_nodes, d_adj, d_fac, d_site),
        surrogate_path,
        input_names=["node_features", "adjacency", "facade_params", "site_cond"],
        output_names=["metrics", "pod_airflow_coeffs"],
        dynamic_axes={
            "node_features": {0: "batch"},
            "adjacency": {0: "batch"},
            "facade_params": {0: "batch"},
            "site_cond": {0: "batch"}
        },
        opset_version=17
    )

    torch.onnx.export(
        generator,
        (d_z, d_adj, d_site, d_perf),
        generator_path,
        input_names=["latent_z", "adjacency", "site_cond", "target_metrics"],
        output_names=["generated_plan_nodes", "generated_facade_params"],
        dynamic_axes={
            "latent_z": {0: "batch"},
            "adjacency": {0: "batch"},
            "site_cond": {0: "batch"},
            "target_metrics": {0: "batch"}
        },
        opset_version=17
    )
    print(f"[OK] Exported {surrogate_path} and {generator_path}")


if __name__ == "__main__":
    export_hybrid_pipeline()
