#!/usr/bin/env python3
"""高逼格 XMind 8 逻辑图：大字号、短文案、深蓝金配色。"""

from __future__ import annotations

import uuid
import zipfile
import xml.sax.saxutils as xu
from pathlib import Path

OUT_DIR = Path(__file__).resolve().parents[1] / "docs"

# 商务色：午夜蓝 + 香槟金 + 冷灰层级
C = {
    "root": dict(
        fill="#0B1F3A",
        border="#C9A227",
        font="#F5F0E6",
        size="20pt",
        weight="700",
        shape="org.xmind.topicShape.roundedRect",
    ),
    "L1": dict(size="14pt", weight="700", shape="org.xmind.topicShape.roundedRect"),
    "L2": dict(size="12pt", weight="600", shape="org.xmind.topicShape.roundedRect"),
    "L3": dict(size="11pt", weight="600", shape="org.xmind.topicShape.roundedRect"),
    "leaf": dict(
        fill="#FFFFFF",
        border="#E6EAF0",
        font="#2C3E50",
        size="11pt",
        weight="400",
        shape="org.xmind.topicShape.roundedRect",
    ),
}

SECTIONS = [
    dict(
        key="option",
        title="①  期权",
        marker="flag-red",
        fill="#FFF5F5",
        border="#C0392B",
        font="#7B241C",
        kids="option",
    ),
    dict(
        key="perp",
        title="②  永续合约",
        marker="flag-blue",
        fill="#F4F8FC",
        border="#1F4E79",
        font="#1A365D",
        kids="perp",
    ),
    dict(
        key="design",
        title="③  设计理念",
        marker="flag-purple",
        fill="#F8F5FB",
        border="#6C3483",
        font="#4A235A",
        kids="design",
    ),
    dict(
        key="capital",
        title="④  资金要求",
        marker="flag-green",
        fill="#F3FAF6",
        border="#1E8449",
        font="#145A32",
        kids="capital",
    ),
    dict(
        key="ops",
        title="⑤  操盘思路",
        marker="flag-orange",
        fill="#FFFBF2",
        border="#B9770E",
        font="#7E5109",
        kids="ops",
    ),
    dict(
        key="risk",
        title="⑥  风险评估",
        marker="flag-yellow",
        fill="#FFFCF2",
        border="#B7950B",
        font="#7D6608",
        kids="risk",
    ),
    dict(
        key="discipline",
        title="⑦  纪律要求",
        marker="people-blue",
        fill="#F4F6F7",
        border="#566573",
        font="#2C3E50",
        kids="discipline",
    ),
]


def nid() -> str:
    return uuid.uuid4().hex[:16]


def sty(sid: str, fill: str, border: str, font: str, size: str, weight: str, shape: str) -> str:
    return (
        f'<style id="{sid}" type="topic"><topic-properties '
        f'shape-class="{shape}" svg:fill="{fill}" '
        f'border-line-color="{border}" border-line-width="2pt" '
        f'fo:color="{font}" fo:font-family="Microsoft YaHei UI,Microsoft YaHei,SansSerif" '
        f'fo:font-size="{size}" fo:font-weight="{weight}" '
        f'line-class="org.xmind.branchConnection.roundedelbow" '
        f'line-color="{border}" line-width="2pt" '
        f'margin-top="4pt" margin-bottom="4pt" '
        f'margin-left="12pt" margin-right="12pt"/>'
        f"</style>"
    )


def styles_xml() -> str:
    chunks = [
        '<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
        '<xmap-styles xmlns="urn:xmind:xmap:xmlns:style:2.0"',
        ' xmlns:fo="http://www.w3.org/1999/XSL/Format"',
        ' xmlns:svg="http://www.w3.org/2000/svg" version="2.0">',
        "  <automatic-styles>",
        '    <style id="map-luxury" type="map">',
        '      <map-properties multi-line-colors="#C9A227 #1F4E79 #1E8449 #B9770E #6C3483 #C0392B #566573"',
        '       color-gradient="none" line-tapered="none" svg:fill="#EEF2F6"/>',
        "    </style>",
        "  </automatic-styles>",
        "  <styles>",
        "    "
        + sty(
            "sty-root",
            C["root"]["fill"],
            C["root"]["border"],
            C["root"]["font"],
            C["root"]["size"],
            C["root"]["weight"],
            C["root"]["shape"],
        ),
        "    "
        + sty(
            "sty-leaf",
            C["leaf"]["fill"],
            C["leaf"]["border"],
            C["leaf"]["font"],
            C["leaf"]["size"],
            C["leaf"]["weight"],
            C["leaf"]["shape"],
        ),
        "    "
        + sty("sty-pro", "#EAF7EF", "#1E8449", "#145A32", "12pt", "600", C["L2"]["shape"]),
        "    "
        + sty("sty-con", "#FDEDEC", "#C0392B", "#7B241C", "12pt", "600", C["L2"]["shape"]),
        "    "
        + sty("sty-sub", "#FFFFFF", "#C9A227", "#0B1F3A", "12pt", "600", C["L2"]["shape"]),
    ]
    for s in SECTIONS:
        chunks.append(
            "    "
            + sty(
                f"sty-{s['key']}",
                s["fill"],
                s["border"],
                s["font"],
                C["L1"]["size"],
                C["L1"]["weight"],
                C["L1"]["shape"],
            )
        )
    # 操盘二级强调色
    for key, fill, border, font in [
        ("ops_mind", "#FFF8E1", "#F4D03F", "#7D6608"),
        ("ops_pos", "#E8F6F3", "#16A085", "#0E6655"),
        ("ops_open", "#E9F7EF", "#27AE60", "#196F3D"),
        ("ops_close", "#F5EEF8", "#8E44AD", "#5B2C6F"),
        ("ops_param", "#EBF5FB", "#2980B9", "#1A5276"),
    ]:
        chunks.append(
            "    " + sty(f"sty-{key}", fill, border, font, "12pt", "600", C["L2"]["shape"])
        )
    chunks.append("  </styles>")
    chunks.append("</xmap-styles>\n")
    return "\n".join(chunks)


def topic(
    title: str,
    children: list[str] | None = None,
    *,
    style_id: str | None = None,
    marker: str | None = None,
    structure_class: str | None = None,
    tid: str | None = None,
) -> str:
    tid = tid or nid()
    attrs = [f'id="{tid}"']
    if structure_class:
        attrs.append(f'structure-class="{structure_class}"')
    if style_id:
        attrs.append(f'style-id="{style_id}"')
    body = f"<title>{xu.escape(title)}</title>"
    if marker:
        body += f'<marker-refs><marker-ref marker-id="{marker}"/></marker-refs>'
    if children:
        body += (
            '<children><topics type="attached">'
            + "".join(children)
            + "</topics></children>"
        )
    return f"<topic {' '.join(attrs)}>{body}</topic>"


def L(text: str) -> str:
    """叶子：一句一事，便于放大后清晰。"""
    return topic(text, style_id="sty-leaf")


def G(title: str, lines: list[str], style: str = "sub", marker: str | None = None) -> str:
    return topic(title, [L(x) for x in lines], style_id=f"sty-{style}", marker=marker)


def bodies() -> dict[str, list[str]]:
    return {
        "option": [
            G(
                "优势",
                [
                    "亏损有界：最多权利金 + 费用",
                    "大波动弹性强，利润空间大",
                    "小资金撬动名义（租金模型）",
                    "按 1 ETH 计价，盈亏直觉清晰",
                    "只做买方，无卖方义务",
                ],
                "pro",
                "symbol-plus",
            ),
            G(
                "劣势",
                [
                    "横盘磨损：不动也在付租金",
                    "盘口薄，点差与滑点明显",
                    "平仓依赖买一流动性",
                    "ATM 档位粗时偏离现价",
                    "临近到期，盘口易恶化",
                ],
                "con",
                "symbol-minus",
            ),
        ],
        "perp": [
            G(
                "优势",
                [
                    "流动性深，开平接近瞬时",
                    "反向持仓，对冲路径清晰",
                    "杠杆可配（默认 3x）",
                    "与期权组成完整对冲组",
                ],
                "pro",
                "symbol-plus",
            ),
            G(
                "劣势",
                [
                    "保证金与强平风险",
                    "实盘有资金费率",
                    "急涨急跌挤压保证金",
                    "横盘几乎不帮权利金回本",
                    "固定 1:2，并非精确中性",
                ],
                "con",
                "symbol-minus",
            ),
        ],
        "design": [
            L("结构：ATM 期权买方 + 反向永续"),
            L("波动大 → 多轮兑现净利"),
            L("波动小 → 接受权利金磨损"),
            L("到期自动全平 · 概率样本思维"),
            L("吃波动，不赌横盘"),
            G(
                "权利金 = 租金",
                [
                    "统一按 1 ETH 名义核算",
                    "例：20U 租 1870 看涨",
                    "现价到 1970 → 约值 100，毛利约 80",
                ],
            ),
            L("规则确定：周末跳过 · 到期强平"),
            L("出场看费用后净利，不看虚盈"),
            L("单组纪律 · 薄腿优先（先期权后永续）"),
            L("SIM：公共行情 + 本地撮合，不下真单"),
        ],
        "capital": [
            L("默认仓：永续 1 ETH + 期权 2 ETH · 3x"),
            G(
                "单组占用（ETH ≈ 1800–2200）",
                [
                    "保证金 ≈ 600–750",
                    "权利金 ≈ 15–60",
                    "费用缓冲 20–50",
                    "波动缓冲 300–800",
                ],
            ),
            G(
                "建议权益",
                [
                    "≥ 1500 仅下限（不推荐）",
                    "≥ 3000 最小可用",
                    "≥ 5000 推荐起步",
                    "≥ 10000 较舒适",
                ],
                "pro",
                "star-green",
            ),
            L("仓位减半，资金约减半（费用占比升）"),
            L("试跑建议：永续 0.1 / 期权 0.2"),
            L("SIM 权益 10000 ≠ 实盘入金"),
            L("按磨损日留余量，勿按每组必赚 15"),
        ],
        "ops": [
            topic(
                "心法与节奏",
                [
                    L("有波动多做，磨盘日可认亏"),
                    L("不追求每日固定轮次"),
                    L("小目标高频兑现（默认 15U）"),
                    L("磨损按预算接受，不报复加仓"),
                    L("全天可开 · 周末默认停开"),
                    L("全平后休息 5 分钟再开下一组"),
                    L("同时最多 1 组，无日轮次上限"),
                ],
                style_id="sty-ops_mind",
                marker="symbol-attention",
            ),
            topic(
                "仓位结构",
                [
                    L("永续 1 ETH · 市价 · 3x"),
                    L("期权 2 ETH · 只买"),
                    L("开吃卖一 · 平吃买一"),
                    L("开仓：期权 → 永续（失败回滚）"),
                    L("平仓：期权 → 永续（对冲先留）"),
                    G(
                        "选向（ATM vs 现价）",
                        [
                            "ATM 偏低 → Call + 空永续",
                            "ATM 偏高 → Put + 多永续",
                            "贴平 → 卖一更高侧开仓",
                            "卖一相等 → 等待",
                        ],
                    ),
                ],
                style_id="sty-ops_pos",
                marker="symbol-plus",
            ),
            topic(
                "开仓",
                [
                    G(
                        "前置",
                        [
                            "策略运行中",
                            "无持仓 · 非休息期",
                            "非周末跳过",
                        ],
                    ),
                    G(
                        "选约",
                        [
                            "剩余时长 ≥ 12h",
                            "取该到期 ATM",
                            "现价/卖一 ≥ 100",
                            "不合格约 → 等待",
                        ],
                    ),
                    G(
                        "执行",
                        [
                            "① 吃卖一开期权",
                            "② 市价开永续",
                            "③ 失败则回滚期权",
                            "④ 锁定初始权利金",
                        ],
                    ),
                ],
                style_id="sty-ops_open",
                marker="arrow-up",
            ),
            topic(
                "平仓",
                [
                    L("两大类：目标平仓 · 到期平仓"),
                    G(
                        "目标A · 双腿全平",
                        [
                            "净利达标 + 期权非远虚",
                            "先期权后永续",
                            "流动性闸门：深度 + 标记偏差30%",
                        ],
                    ),
                    G(
                        "目标B · 只平永续",
                        [
                            "净利达标 + 期权远虚(内在约0)",
                            "100x杠杆下波动约1%常远虚",
                            "期权归档到期 · 不再盯盘",
                            "逐仓不挡下一组开仓",
                            "下一组不扫描残留期权",
                            "下方盘口切回新ATM",
                            "到期按内在价值结算",
                        ],
                    ),
                    G(
                        "到期平仓",
                        [
                            "目标未达标 → 持有到到期",
                            "期权按内在价值结算",
                            "活跃组若有永续一并平",
                            "残留组只结归档期权",
                        ],
                    ),
                    G(
                        "页面监控分工",
                        [
                            "有活跃仓 → 钉持仓行权价",
                            "空仓 → 跟现价ATM(≥5点切换)",
                            "仅残留 → 列表显示归档腿",
                            "仅残留 → 下方仍是新ATM",
                            "浮盈亏只用持仓合约买一",
                        ],
                    ),
                    L("另有：紧急全平 / 手动平仓"),
                ],
                style_id="sty-ops_close",
                marker="arrow-down",
            ),
            topic(
                "关键参数",
                [
                    L("仓位 1 / 2 · 杠杆 3 · 费率 0.05%"),
                    L("出场 fixed_usdt · 目标 15U"),
                    L("休息 300s · 周末跳过"),
                    L("选约 12h · 杠杆门槛 100"),
                    L("ATM 偏差默认关 · 平仓偏差 30%"),
                    L("行情：OKX 默认 · 币安可切"),
                ],
                style_id="sty-ops_param",
                marker="symbol-question",
            ),
        ],
        "risk": [
            G(
                "主要风险",
                [
                    "高 · 横盘磨损权利金",
                    "高 · SIM 难直接外推实盘",
                    "中高 · 方向选错",
                    "中 · 费用侵蚀小目标",
                    "中 · 期权流动性",
                    "中 · 保证金挤压",
                    "中 · 行情中断 / 限流",
                ],
                "con",
                "symbol-exclam",
            ),
            G(
                "缓解",
                [
                    "周末跳过 · 到期认亏",
                    "净利达标才兑现",
                    "目标覆盖费用 · 留缓冲",
                    "小资金验证后再放大",
                ],
                "pro",
                "symbol-right",
            ),
            G(
                "边界",
                [
                    "不做卖方",
                    "不做横盘智能过滤",
                    "不做多组并行",
                    "实盘另需授权确认",
                ],
            ),
        ],
        "discipline": [
            L("有仓不开下一组"),
            L("全平后休息满 5 分钟"),
            L("周末不新开，持仓可平"),
            L("常规平仓不硬砸流动性"),
            L("只看出场净利，不看虚盈"),
            L("磨损日认账，不报复交易"),
            L("不临时叠仓、不加卖权"),
            L("SIM 跑通再谈实盘放大"),
        ],
    }


def mm_escape(s: str) -> str:
    return xu.escape(s).replace('"', "&quot;")


def mm(title: str, children: list[str] | None = None) -> str:
    t = mm_escape(title)
    if not children:
        return f'<node TEXT="{t}"/>'
    return f'<node TEXT="{t}">{"".join(children)}</node>'


def mm_flat() -> list[str]:
    """Freemind：短句版，便于导入后手调样式。"""
    b = bodies()

    def walk_xml_titles(xml_topics: list[str]) -> list[str]:
        # bodies 已是 xmind xml；mm 另建短树
        return []

    _ = walk_xml_titles
    return [
        mm(
            "①  期权",
            [
                mm("优势", [mm(x) for x in ["亏损有界", "大波动弹性强", "租金模型", "1 ETH 好算", "只做买方"]]),
                mm("劣势", [mm(x) for x in ["横盘磨损", "盘口薄", "买一依赖", "ATM 偏离", "临期恶化"]]),
            ],
        ),
        mm(
            "②  永续合约",
            [
                mm("优势", [mm(x) for x in ["流动性深", "对冲清晰", "杠杆可配", "组成对冲组"]]),
                mm("劣势", [mm(x) for x in ["强平风险", "资金费率", "保证金挤压", "横盘无助", "1:2 非精确"]]),
            ],
        ),
        mm("③  设计理念", [mm(x) for x in [
            "ATM 买方 + 反向永续",
            "波动兑现 · 磨损认账",
            "到期全平 · 概率样本",
            "权利金 = 租金",
            "薄腿优先 · 单组纪律",
        ]]),
        mm("④  资金要求", [mm(x) for x in [
            "默认 1+2 ETH · 3x",
            "推荐权益 ≥ 5000",
            "舒适 ≥ 10000",
            "试跑 0.1 / 0.2",
        ]]),
        mm(
            "⑤  操盘思路",
            [
                mm("心法与节奏", [mm(x) for x in ["有波动多做", "目标 15U", "休息 5 分钟", "最多 1 组"]]),
                mm("仓位结构", [mm(x) for x in ["永续 1 · 期权 2", "先期权后永续", "ATM 选向"]]),
                mm("开仓", [mm(x) for x in ["前置检查", "选约门槛", "四步执行"]]),
                mm("平仓", [mm(x) for x in ["目标A双腿", "目标B只平永续", "到期结算", "监控分工"]]),
                mm("关键参数", [mm(x) for x in ["仓位/杠杆/费率", "目标/休息/周末", "选约/偏差"]]),
            ],
        ),
        mm("⑥  风险评估", [mm(x) for x in ["横盘磨损", "SIM≠实盘", "方向/流动性/保证金", "残留不挡新开"]]),
        mm("⑦  纪律要求", [mm(x) for x in [
            "活跃1组 · 残留可共存",
            "远虚归档 · 不挡新开",
            "有仓钉持仓 · 空仓跟ATM",
            "残留列表 · 下方新ATM",
            "看净利 · 不报复",
            "SIM 后再放大",
        ]]),
    ]


def main() -> None:
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    data = bodies()
    branches = []
    for s in SECTIONS:
        branches.append(
            topic(
                s["title"],
                data[s["kids"]],
                style_id=f"sty-{s['key']}",
                marker=s["marker"],
            )
        )

    # 中心主题：星号标记 = XMind marker-id "star"
    root = topic(
        "ETH 永续 + 期权对冲",
        branches,
        tid=nid(),
        structure_class="org.xmind.ui.logic.right",
        style_id="sty-root",
        marker="star",
    )

    sheet_id = nid()
    content = (
        '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
        '<xmap-content xmlns="urn:xmind:xmap:xmlns:content:2.0" version="2.0">\n'
        f'  <sheet id="{sheet_id}" theme="map-luxury" timestamp="0">\n'
        f"    {root}\n"
        "    <title>策略逻辑图 · 高清版</title>\n"
        "  </sheet>\n"
        "</xmap-content>\n"
    )
    meta = (
        '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
        '<meta xmlns="urn:xmind:xmap:xmlns:meta:2.0" version="2.0">\n'
        "  <Author>eth_hedge_sim</Author>\n"
        "</meta>\n"
    )
    manifest = (
        '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
        '<manifest xmlns="urn:xmind:xmap:xmlns:manifest:1.0">\n'
        '  <file-entry full-path="content.xml" media-type="text/xml"/>\n'
        '  <file-entry full-path="styles.xml" media-type="text/xml"/>\n'
        '  <file-entry full-path="meta.xml" media-type="text/xml"/>\n'
        '  <file-entry full-path="META-INF/" media-type=""/>\n'
        '  <file-entry full-path="META-INF/manifest.xml" media-type="text/xml"/>\n'
        "</manifest>\n"
    )

    xmind_path = OUT_DIR / "ETH对冲策略说明.xmind"
    with zipfile.ZipFile(xmind_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("content.xml", content.encode("utf-8"))
        zf.writestr("styles.xml", styles_xml().encode("utf-8"))
        zf.writestr("meta.xml", meta.encode("utf-8"))
        zf.writestr("META-INF/manifest.xml", manifest.encode("utf-8"))

    mm_path = OUT_DIR / "ETH对冲策略说明.mm"
    mm_path.write_text(
        '<?xml version="1.0" encoding="UTF-8"?>\n'
        '<map version="1.0.1">\n'
        f"{mm('★ ETH 永续 + 期权对冲', mm_flat())}\n"
        "</map>\n",
        encoding="utf-8",
    )
    print("OK", xmind_path)
    print("OK", mm_path)
    print("Root marker: star (星号)")


if __name__ == "__main__":
    main()
