"""
lambdagentpaas 系统架构图 — PPT 优化版 v3

修复:
  - 卡片标题与正文留足间距 (title_gap 参数)
  - 显式计算文字实际占位, 防止与 va 基准点的计算误差
  - 增大所有卡片高度, 给正文足够空间
  - L1/L2 层高度相应增加
  - 所有尺寸支持 SCALE 参数统一缩放

用法:
    python3 docs/assets/draw_architecture.py
    python3 docs/assets/draw_architecture.py --scale 1.5    # 放大 1.5 倍
    python3 docs/assets/draw_architecture.py --scale 0.8    # 缩小到 0.8 倍
"""

import argparse
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, Rectangle


plt.rcParams["font.sans-serif"] = [
    "PingFang HK", "PingFang SC", "Hiragino Sans GB",
    "STHeiti", "Heiti TC", "Songti SC", "Arial Unicode MS",
]
plt.rcParams["axes.unicode_minus"] = False


# ═══════════════════════════════════════════════════════════
# 配色
# ═══════════════════════════════════════════════════════════

C = {
    "L3_bg":     "#FFF4E0", "L3_edge": "#E67E22", "L3_header": "#F39C12",
    "L3_card":   "#FFD79A",
    "L2_bg":     "#E3F2FD", "L2_edge": "#1976D2", "L2_header": "#1E88E5",
    "L2_card":   "#A8D0F2",
    "L2_safety_bg": "#FFEBEE", "L2_safety_edge": "#C62828",
    "L2_safety_header": "#E53935", "L2_safety_card": "#FFCDD2",
    "L1_bg":     "#F3E5F5", "L1_edge": "#7B1FA2", "L1_header": "#8E24AA",
    "L1_card":   "#D7A8E0", "L1_paper": "#C789D3",
    "L0_bg":     "#E8F5E9", "L0_edge": "#388E3C", "L0_header": "#43A047",
    "L0_card":   "#9FD9A5",
    "text":      "#1A1A1A",
    "text_mid":  "#37474F",
    "text_light":"#FFFFFF",
    "arrow":     "#263238",
}


# ═══════════════════════════════════════════════════════════
# 绘图辅助
# ═══════════════════════════════════════════════════════════

class Scaler:
    """统一缩放器 — 一个参数控制所有尺寸。"""
    def __init__(self, scale: float = 1.0):
        self.s = scale

    def fs(self, size: int) -> float:
        """字号缩放"""
        return size * self.s

    def lw(self, width: float) -> float:
        """线宽缩放"""
        return width * self.s

    # 数据单位的坐标 / 尺寸保持不变 (ax 坐标系固定 0-20, 0-15.5)
    # 只需要让 figsize 按 scale 变化即可让输出像素 = scale × 原始像素
    def figsize(self, w: float, h: float):
        return (w * self.s, h * self.s)


def layer(ax, s: Scaler, x, y, w, h, header_h, label, bg, edge, header_color):
    """层: 外框 + 顶部 header 带"""
    ax.add_patch(FancyBboxPatch(
        (x, y), w, h,
        boxstyle="round,pad=0.08,rounding_size=0.18",
        facecolor=bg, edgecolor=edge, linewidth=s.lw(2.5), alpha=0.5, zorder=1,
    ))
    header_y = y + h - header_h
    ax.add_patch(Rectangle(
        (x + 0.1, header_y + 0.05), w - 0.2, header_h - 0.1,
        facecolor=header_color, edgecolor="none", alpha=0.92, zorder=2,
    ))
    ax.text(
        x + w / 2, header_y + header_h / 2, label,
        ha="center", va="center",
        fontsize=s.fs(17), fontweight="bold",
        color=C["text_light"], zorder=5,
    )


def card(ax, s: Scaler, x, y, w, h, title, lines, face, edge,
         title_size=15, body_size=12,
         pad_top=0.22, title_gap=0.28):
    """
    组件卡片 — 正确的文字布局

    如果有 lines (正文):
      - 标题 va='top', 放在卡片顶部 (距顶 pad_top)
      - 正文 va='top', 在标题下方 title_gap + 标题高度 处
    否则:
      - 标题居中显示
    """
    ax.add_patch(FancyBboxPatch(
        (x, y), w, h,
        boxstyle="round,pad=0.06,rounding_size=0.12",
        facecolor=face, edgecolor=edge, linewidth=s.lw(1.8), zorder=3,
    ))

    if lines:
        title_h = title_size / 72.0
        title_top_y = y + h - pad_top
        ax.text(
            x + w / 2, title_top_y, title,
            ha="center", va="top",
            fontsize=s.fs(title_size), fontweight="bold",
            color=C["text"], zorder=4,
        )
        body_top_y = title_top_y - title_h - title_gap
        body = "\n".join(lines)
        ax.text(
            x + w / 2, body_top_y, body,
            ha="center", va="top",
            fontsize=s.fs(body_size), color=C["text_mid"],
            linespacing=1.55, zorder=4,
        )
    else:
        ax.text(
            x + w / 2, y + h / 2, title,
            ha="center", va="center",
            fontsize=s.fs(title_size), fontweight="bold",
            color=C["text"], zorder=4,
        )


def arrow_down(ax, s: Scaler, x, y_top, y_bot, color=None, lw=3):
    ax.annotate(
        "", xy=(x, y_bot), xytext=(x, y_top),
        arrowprops=dict(arrowstyle="-|>", color=color or C["arrow"],
                        lw=s.lw(lw), mutation_scale=25),
        zorder=2,
    )


# ═══════════════════════════════════════════════════════════
# 主绘图
# ═══════════════════════════════════════════════════════════

def draw(output: str, scale: float = 1.0):
    """
    绘制架构图。

    Args:
        output: 输出 PNG 路径
        scale:  全局缩放比例 (1.0 = 默认; 1.5 = 放大; 0.8 = 缩小)
                所有字号、线宽、figsize 按此比例缩放
    """
    s = Scaler(scale)
    BASE_W, BASE_H = 20, 15.5  # 基础画布尺寸 (数据单位)

    fig, ax = plt.subplots(figsize=s.figsize(BASE_W, BASE_H))
    ax.set_xlim(0, BASE_W)
    ax.set_ylim(0, BASE_H)
    ax.axis("off")

    # ━━━ 顶部标题 ━━━
    ax.text(10, 15.0, "lambdagentpaas 系统架构",
            ha="center", va="center",
            fontsize=s.fs(28), fontweight="bold", color=C["text"])
    ax.text(10, 14.45, "形式化 λA 演算驱动的智能体编程语言 + PaaS 平台",
            ha="center", va="center",
            fontsize=s.fs(15), color=C["text_mid"], style="italic")

    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    # L3 应用层      y=11.3 ~ 13.95   h=2.65   header 0.6
    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    layer(ax, s, 0.3, 11.3, 19.4, 2.65, 0.6,
          "L3   应用层 — 真实案例",
          C["L3_bg"], C["L3_edge"], C["L3_header"])

    card(ax, s, 0.9, 11.45, 5.9, 1.85,
         "qaagent67wiki",
         ["海事法规问答", "1326 源文件 / 2989 wiki 页"],
         C["L3_card"], C["L3_edge"], title_size=18, body_size=13)

    card(ax, s, 7.05, 11.45, 5.9, 1.85,
         "agent67v2",
         ["多智能体编程助手", "42 工具 / 5 个子智能体"],
         C["L3_card"], C["L3_edge"], title_size=18, body_size=13)

    card(ax, s, 13.2, 11.45, 5.9, 1.85,
         "research67",
         ["科研助手 (IDEA → 论文)", "8 个子智能体 / 迭代优化"],
         C["L3_card"], C["L3_edge"], title_size=18, body_size=13)

    # L3 → L2
    for x in (3.85, 10, 16.15):
        arrow_down(ax, s, x, 11.25, 10.8)

    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    # L2 工程实现    y=5.0 ~ 10.75    h=5.75   header 0.55
    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    layer(ax, s, 0.3, 5.0, 19.4, 5.75, 0.55,
          "L2   工程实现 (agentpaas)",
          C["L2_bg"], C["L2_edge"], C["L2_header"])

    card(ax, s, 0.9, 9.05, 18.2, 1.0,
         "REST API (28 端点)  +  MCP Server (5 工具)  +  GitHub Action",
         [], C["L2_card"], C["L2_edge"], title_size=16)

    card(ax, s, 0.9, 7.2, 5.9, 1.7,
         "Instance 机制",
         ["template / instance", "YAML 深度合并 + workspace"],
         C["L2_card"], C["L2_edge"], title_size=15, body_size=12)

    card(ax, s, 7.05, 7.2, 5.9, 1.7,
         "双引擎执行",
         ["Recursive / CEK / Adaptive", "Python 栈 vs CEK Machine"],
         C["L2_card"], C["L2_edge"], title_size=15, body_size=12)

    card(ax, s, 13.2, 7.2, 5.9, 1.7,
         "多租户 + 安全",
         ["RBAC + Quota + Rate Limit", "审计日志 + API Key"],
         C["L2_card"], C["L2_edge"], title_size=15, body_size=12)

    # ── Safety Net ──
    safety_x, safety_y, safety_w, safety_h = 0.9, 5.15, 18.2, 1.9
    ax.add_patch(FancyBboxPatch(
        (safety_x, safety_y), safety_w, safety_h,
        boxstyle="round,pad=0.06,rounding_size=0.15",
        facecolor=C["L2_safety_bg"], edgecolor=C["L2_safety_edge"],
        linewidth=s.lw(2), zorder=3,
    ))
    safety_header_h = 0.5
    ax.add_patch(Rectangle(
        (safety_x + 0.1, safety_y + safety_h - safety_header_h - 0.05),
        safety_w - 0.2, safety_header_h - 0.05,
        facecolor=C["L2_safety_header"], edgecolor="none",
        alpha=0.92, zorder=4,
    ))
    ax.text(
        10, safety_y + safety_h - safety_header_h / 2 - 0.05,
        "编译时四道安全网  Static Analysis",
        ha="center", va="center",
        fontsize=s.fs(15), fontweight="bold",
        color=C["text_light"], zorder=5,
    )

    checks = [
        ("Lint\n26 条规则",        2.0),
        ("Type Check\nT-Compose",  6.1),
        ("Cost Predict\n分级类型",  10.2),
        ("Store Indep.\nPair 汇合", 14.3),
    ]
    check_y, check_h = 5.25, 1.1
    for label, cx in checks:
        ax.add_patch(FancyBboxPatch(
            (cx, check_y), 3.0, check_h,
            boxstyle="round,pad=0.04,rounding_size=0.1",
            facecolor=C["L2_safety_card"],
            edgecolor=C["L2_safety_edge"],
            linewidth=s.lw(1.5), zorder=5,
        ))
        ax.text(
            cx + 1.5, check_y + check_h / 2, label,
            ha="center", va="center",
            fontsize=s.fs(13), fontweight="bold",
            color=C["text"], zorder=6, linespacing=1.5,
        )

    mid_y = check_y + check_h / 2
    for i in range(3):
        x1 = checks[i][1] + 3.0
        x2 = checks[i+1][1]
        ax.annotate(
            "", xy=(x2, mid_y), xytext=(x1, mid_y),
            arrowprops=dict(arrowstyle="->",
                            color=C["L2_safety_edge"], lw=s.lw(2.5)),
            zorder=6,
        )
    ax.annotate(
        "", xy=(18.0, mid_y), xytext=(17.3, mid_y),
        arrowprops=dict(arrowstyle="->",
                        color=C["L2_safety_edge"], lw=s.lw(2.5)),
        zorder=6,
    )
    ax.text(18.05, mid_y, " PASS", ha="left", va="center",
            fontsize=s.fs(14), fontweight="bold", color="#2E7D32", zorder=7)

    # L2 → L1
    for x in (5, 10, 15):
        arrow_down(ax, s, x, 4.95, 4.55)

    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    # L1 理论技术    y=1.5 ~ 4.5    h=3.0    header 0.55
    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    layer(ax, s, 0.3, 1.5, 19.4, 3.0, 0.55,
          "L1   理论技术 (lambdagent / λA 演算)",
          C["L1_bg"], C["L1_edge"], C["L1_header"])

    card(ax, s, 0.9, 2.55, 5.9, 1.35,
         "三层编程模型",
         ["Skill → Pattern → Orchestration"],
         C["L1_card"], C["L1_edge"], title_size=15, body_size=12)

    card(ax, s, 7.05, 2.55, 5.9, 1.35,
         "λA 构造子  11 + 5",
         ["Lam / Tool / Compose / Loop / Route / Pair / ..."],
         C["L1_card"], C["L1_edge"], title_size=15, body_size=12)

    card(ax, s, 13.2, 2.55, 5.9, 1.35,
         "6 个协作模式",
         ["review / fan_out / pipeline / escalation / ..."],
         C["L1_card"], C["L1_edge"], title_size=15, body_size=12)

    card(ax, s, 0.9, 1.6, 18.2, 0.9,
         "形式化理论基础 — 三篇论文",
         [],
         C["L1_paper"], C["L1_edge"], title_size=15)
    ax.text(
        10, 1.78,
        "Paper I  λA 类型系统     |     Paper II  操作语义 + CEK     |     Paper III  类型与效果 + 分级类型",
        ha="center", va="center",
        fontsize=s.fs(12), color=C["text_mid"], style="italic", zorder=5,
    )

    # L1 → L0
    for x in (5, 10, 15):
        arrow_down(ax, s, x, 1.45, 1.15)

    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    # L0 基础设施    y=0.1 ~ 1.1    h=1.0    header 0.4
    # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    layer(ax, s, 0.3, 0.1, 19.4, 1.0, 0.4,
          "L0   基础设施",
          C["L0_bg"], C["L0_edge"], C["L0_header"])

    card(ax, s, 0.9, 0.2, 5.9, 0.48, "LLM Providers",
         [], C["L0_card"], C["L0_edge"], title_size=14)
    card(ax, s, 7.05, 0.2, 5.9, 0.48, "Storage",
         [], C["L0_card"], C["L0_edge"], title_size=14)
    card(ax, s, 13.2, 0.2, 5.9, 0.48, "Integration",
         [], C["L0_card"], C["L0_edge"], title_size=14)

    # 保存
    os.makedirs(os.path.dirname(output), exist_ok=True)
    plt.savefig(output, dpi=180, bbox_inches="tight",
                facecolor="white", edgecolor="none")
    plt.close(fig)
    print(f"✅ Saved: {output}  (scale={scale})")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Draw lambdagentpaas architecture diagram")
    parser.add_argument("--scale", type=float, default=1.0,
                        help="全局缩放比例 (1.0=默认, 1.5=放大, 0.8=缩小)")
    parser.add_argument("--output", type=str, default=None,
                        help="输出路径 (默认: docs/assets/lambdagentpaas-architecture.png)")
    args = parser.parse_args()

    here = os.path.dirname(os.path.abspath(__file__))
    output = args.output or os.path.join(here, "lambdagentpaas-architecture.png")
    draw(output, scale=args.scale)
