from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
import re
from typing import Any

from . import book_program_adapters, flood_mainline, river_special
from .export import export_calculation_book_docx


@dataclass(frozen=True)
class ParameterEvidence:
    name: str
    value: str
    unit: str
    source: str
    clause: str
    reason: str


@dataclass(frozen=True)
class CalcBookDocument:
    cb_id: str
    title: str
    project_name: str
    content: str
    figures: list[tuple[str, str]]
    word_count: int
    page_estimate: int
    parameter_count: int
    sourced_parameter_count: int

    @property
    def parameter_source_coverage(self) -> float:
        if self.parameter_count <= 0:
            return 1.0
        return self.sourced_parameter_count / self.parameter_count


DEFAULT_OUTPUT_DIR = Path(r"D:\workspace\b9_calc_books")


SAMPLE_PARAMS: dict[str, dict[str, Any]] = {
    "1-01": {
        "area_km2": 47.02,
        "rainfall_zone_id": "huanghe",
        "frequencies": "100,50,20,10",
        "h1_mean_mm": 38.5,
        "h1_cv": 0.45,
        "h6_mean_mm": 64.0,
        "h6_cv": 0.50,
        "h24_mean_mm": 95.0,
        "h24_cv": 0.55,
        "cs_ratio": 3.5,
        "alpha1": 1.0,
        "alpha3": 1.0,
        "alpha6": 1.0,
        "alpha24": 1.0,
    },
    "1-04": {
        "area_km2": 47.02,
        "river_slope_permille": 95.724,
        "rainfall_zone_id": "huanghe",
        "frequencies": "100,50,20,10",
        "h1_mean_mm": 38.5,
        "h1_cv": 0.45,
        "h6_mean_mm": 64.0,
        "h6_cv": 0.50,
        "h24_mean_mm": 95.0,
        "h24_cv": 0.55,
        "cs_ratio": 3.5,
        "alpha1": 1.0,
        "alpha3": 1.0,
        "alpha6": 1.0,
        "alpha24": 1.0,
        "delta_t_h": 1.0,
        "infiltration_table_id": "huangtu",
        "unit_param_region": "huangtu",
    },
    "1-05": {
        "area_km2": 47.02,
        "river_length_km": 13.8,
        "river_slope_permille": 95.724,
        "rainfall_zone_id": "huanghe",
        "frequencies": "100,50,20,10",
        "h1_mean_mm": 38.5,
        "h1_cv": 0.45,
        "h6_mean_mm": 64.0,
        "h6_cv": 0.50,
        "h24_mean_mm": 95.0,
        "h24_cv": 0.55,
        "cs_ratio": 3.5,
        "alpha1": 1.0,
        "alpha3": 1.0,
        "alpha6": 1.0,
        "alpha24": 1.0,
        "delta_t_h": 1.0,
        "infiltration_table_id": "huangtu",
        "unit_param_region": "huangtu",
        "rational_region_id": "huangtu",
    },
    "4-01": {
        "flow_m3s": 40.0,
        "downstream_water_level_m": 103.5,
        "flow_regime": "subcritical",
        "reach_lengths_m": "300",
        "contraction_coeff": 0.1,
        "expansion_coeff": 0.3,
        "friction_method": "average_conveyance",
    },
}


def generate_calc_book_document(
    cb_id: str,
    project_id: str = "",
    *,
    target_path: str | Path | None = None,
    project_name: str = "B9示例工程",
    version: str = "V1.0.71",
    watermark: str = "送审稿",
) -> dict[str, Any]:
    book = build_calc_book(cb_id, project_id=project_id, project_name=project_name)
    path = Path(target_path) if target_path else DEFAULT_OUTPUT_DIR / f"{book.cb_id}_{_safe_filename(book.title)}.docx"
    export_calculation_book_docx(
        book.content,
        path,
        title=book.title,
        project_name=book.project_name,
        version=version,
        watermark=watermark,
        figures=book.figures,
    )
    return {
        "ok": True,
        "status": "generated",
        "cb_id": book.cb_id,
        "title": book.title,
        "path": str(path),
        "word_count": book.word_count,
        "page_estimate": book.page_estimate,
        "parameter_count": book.parameter_count,
        "sourced_parameter_count": book.sourced_parameter_count,
        "parameter_source_coverage": round(book.parameter_source_coverage, 4),
        "figure_count": len(book.figures),
    }


def build_calc_book(cb_id: str, project_id: str = "", project_name: str = "B9示例工程") -> CalcBookDocument:
    key = cb_id.strip().upper()
    if key == "CB-01":
        return _build_cb_01(project_name)
    if key == "CB-02":
        return _build_cb_02(project_name)
    if key == "CB-03":
        return _build_cb_03(project_name)
    if key == "CB-04":
        return _build_cb_04(project_name)
    if key == "CB-05":
        return _build_cb_05(project_name)
    if key == "CB-06":
        return _build_cb_06(project_name)
    if key == "CB-07":
        return _build_cb_07(project_name)
    if key == "CB-08":
        return _build_cb_08(project_name)
    if key == "CB-09":
        return _build_cb_09(project_name)
    if key == "CB-10":
        return _build_cb_10(project_name)
    if key == "CB-11":
        return _build_cb_11(project_name)
    if key == "CB-12":
        return _build_cb_12(project_name)
    if key == "CB-13":
        return _build_cb_13(project_name)
    if key == "CB-14":
        return _build_cb_14(project_name)
    raise ValueError(f"未知计算书ID：{cb_id}")


def generate_first_batch_examples(output_dir: str | Path = DEFAULT_OUTPUT_DIR) -> list[dict[str, Any]]:
    out = Path(output_dir)
    return [
        generate_calc_book_document("CB-01", target_path=out / "CB-01_设计暴雨计算书.docx"),
        generate_calc_book_document("CB-02", target_path=out / "CB-02_设计洪水计算书.docx"),
        generate_calc_book_document("CB-03", target_path=out / "CB-03_设计洪水位与水面线计算书.docx"),
    ]


def generate_second_batch_examples(output_dir: str | Path = DEFAULT_OUTPUT_DIR) -> list[dict[str, Any]]:
    out = Path(output_dir)
    examples = [
        ("CB-04", "CB-04_调洪演算计算书.docx"),
        ("CB-05", "CB-05_泄洪能力复核计算书.docx"),
        ("CB-06", "CB-06_阻水比计算书.docx"),
        ("CB-07", "CB-07_壅水计算书.docx"),
        ("CB-08", "CB-08_冲刷计算书.docx"),
        ("CB-09", "CB-09_净空校核计算书.docx"),
        ("CB-10", "CB-10_基础埋深安全计算书.docx"),
        ("CB-11", "CB-11_河道演变计算书.docx"),
        ("CB-12", "CB-12_渠道泵站计算书.docx"),
        ("CB-13", "CB-13_水锤计算书.docx"),
        ("CB-14", "CB-14_水工建筑物计算书.docx"),
    ]
    return [generate_calc_book_document(cb_id, target_path=out / filename) for cb_id, filename in examples]


def _build_cb_01(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-01 20260804
    # Generates the detailed seven-chapter design-rainfall calculation book for 1-01.
    params = SAMPLE_PARAMS["1-01"]
    result = flood_mainline.calculate_fld_01(params)
    evidence = [
        ParameterEvidence("流域面积 F", "47.02", "km²", "设计资料", "基础资料复核表", "采用项目汇水边界量算成果，作为雨洪计算控制面积。"),
        ParameterEvidence("雨型分区", "黄河干支流区", "", "地区经验", "甘肃省暴雨洪水图集雨型分区图", "项目位于黄河流域控制区，按图集分区自动取用。"),
        ParameterEvidence("1h 点雨量均值 H1", "38.50", "mm", "地区经验", "暴雨参数等值线图", "用于短历时暴雨强度控制。"),
        ParameterEvidence("6h 点雨量均值 H6", "64.00", "mm", "地区经验", "暴雨参数等值线图", "用于推求 3h 雨量和雨型过程。"),
        ParameterEvidence("24h 点雨量均值 H24", "95.00", "mm", "地区经验", "暴雨参数等值线图", "用于日雨量控制和雨型总量校核。"),
        ParameterEvidence("Cv", "0.45/0.50/0.55", "", "规范查表", "暴雨统计参数表", "按控制历时分别取变差系数。"),
        ParameterEvidence("Cs/Cv", "3.50", "", "规范查表", "皮尔逊 III 型频率曲线参数规定", "暴雨频率计算采用地区常用偏态系数比。"),
        ParameterEvidence("点面折减系数 α", "1.00", "", "规范查表", "点面折减系数表", "本示例流域面积较小，折减按 1.00 取值。"),
    ]
    rows = _flatten_result_tables(result)
    content = _seven_chapter_content(
        title="CB-01 设计暴雨计算书",
        task="本计算书用于复核流域设计暴雨参数，输出不同设计频率下 1h、3h、6h、24h 点雨量、面雨量及雨型选取结果。",
        method="采用皮尔逊 III 型频率曲线推求设计点雨量，3h 雨量按 H3 = H1^0.387 × H6^0.613 推求，面雨量按点雨量乘以点面折减系数和形状修正系数。",
        evidence=evidence,
        steps=[
            ("频率点雨量", "把 Hmean、Cv、Cs/Cv 代入频率曲线，物理意义是把多年平均暴雨量转换为指定重现期控制雨量。", "Pp = Hmean × Kp", "以 24h 为例：95.00 × Kp", "得到各频率 24h 点雨量。"),
            ("3h 雨量插补", "3h 是产流与汇流过程的重要控制历时，软件按图集经验关系由 1h、6h 插补。", "H3 = H1^0.387 × H6^0.613", "38.50^0.387 × 64.00^0.613", "形成 3h 控制点雨量均值。"),
            ("面雨量折减", "面雨量反映整个流域上的平均降雨深，是后续产流计算的输入。", "Hp = Pp × α × γ", "点雨量 × 1.00 × 1.00", "得到流域面雨量成果表。"),
        ],
        result_rows=rows,
        analysis=[
            "100 年、50 年、20 年、10 年频率成果随频率降低而单调递减，符合暴雨频率统计规律。",
            "与地区经验相比，点面折减系数取 1.00 时成果偏保守，适合小流域初步复核。",
            "敏感性分析显示：H24 增减 10% 时，24h 面雨量约同步增减 10%，后续洪峰流量应重点关注 H24 与入渗参数。", 
        ],
        conclusion="1-01 设计暴雨参数来源完整，频率成果可作为瞬时单位线法、推理公式法及设计洪水成果对比的上游输入。",
    )
    figures = [("设计暴雨频率成果折线图", _line_svg("设计暴雨频率成果", [100, 50, 20, 10], _numeric_values(rows[:4])))]
    return _book("CB-01", "设计暴雨计算书", project_name, content, figures, evidence)


def _build_cb_02(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-02 20260804
    # Generates the detailed seven-chapter design-flood comparison calculation book for 1-02/04/05/09/10.
    unit_result = flood_mainline.calculate_fld_04(SAMPLE_PARAMS["1-04"])
    rational_result = flood_mainline.calculate_fld_05(SAMPLE_PARAMS["1-05"])
    unit_peaks = _metric_map(unit_result.get("metrics", []))
    rational_peaks = _metric_map(rational_result.get("metrics", []))
    rows = []
    for freq in ("100", "50", "20", "10"):
        u = unit_peaks.get(freq, 0.0)
        r = rational_peaks.get(freq, 0.0)
        adopted = round(max(u, r), 2)
        rows.append([f"{freq}年", _fmt(u), _fmt(r), _fmt(adopted), "采用较不利值，报告中可结合邻近站进一步复核"])
    evidence = [
        ParameterEvidence("流域面积 F", "47.02", "km²", "设计资料", "汇水边界量算成果", "控制洪量和单位线参数。"),
        ParameterEvidence("主河槽比降 J", "95.724", "‰", "实测/地形量算", "河道纵断面成果", "用于汇流时间和单位线参数推求。"),
        ParameterEvidence("主河道长度 L", "13.80", "km", "实测/地形量算", "河道中心线量测", "推理公式汇流时间计算必需参数。"),
        ParameterEvidence("雨型分区", "黄河干支流区", "", "地区经验", "暴雨洪水图集", "控制暴雨时程分配。"),
        ParameterEvidence("入渗分区", "黄土区", "", "规范查表", "入渗损失参数表", "控制净雨过程和产流强度。"),
        ParameterEvidence("计算步长 Δt", "1.00", "h", "设计资料", "计算精度控制", "与雨型时段和洪水过程线输出一致。"),
    ]
    content = _seven_chapter_content(
        title="CB-02 设计洪水计算书",
        task="本计算书对同一流域分别采用瞬时单位线法、推理公式法等方法计算设计洪峰和洪水过程线，并形成采用值建议。",
        method="瞬时单位线法用于过程线推求，推理公式法用于洪峰与汇流曲线复核；成果以多方法对比、频率一致性和工程偏安全原则综合判定。",
        evidence=evidence,
        steps=[
            ("净雨过程", "面雨量扣除入渗损失后得到净雨，物理意义是直接参与产流的有效降雨。", "Re(t)=Parea(t)-f(t)", "逐时面雨量 - 黄土区入渗损失", "生成净雨过程表。"),
            ("单位线汇流", "净雨经过流域汇流转化为出口断面洪水过程线。", "Q(t)=Σ Re(i)×UH(t-i)", "净雨过程 × Nash 瞬时单位线", "得到各频率洪峰、峰现时间和洪量。"),
            ("推理公式求峰", "按暴雨强度和汇流时间求洪峰，交点为设计洪峰控制点。", "Qm=0.278×φ×i×F", "径流系数 × 暴雨强度 × 47.02", "得到推理公式洪峰并与单位线法对比。"),
            ("采用值判别", "多方法结果差异用于判断参数合理性，工程应用一般采用有依据的偏安全值。", "Qadopt=max(Qunit,Qrational)", "逐频率比较两法成果", "形成设计洪水采用值表。"),
        ],
        result_rows=[["频率", "瞬时单位线 Qp(m³/s)", "推理公式 Qp(m³/s)", "采用值(m³/s)", "采用说明"], *rows],
        analysis=[
            "瞬时单位线法给出完整过程线，可用于调洪、水面线和壅水计算；推理公式法对洪峰更直接，适合小流域快速复核。",
            "若两法洪峰差异超过 20%，报告编写时应复核雨型、入渗参数、汇流时间和地区参数取值。",
            "敏感性分析：雨量整体增加 10% 时洪峰同步上升，入渗率增加 10% 时净雨与洪峰降低，主河槽比降增加将缩短汇流时间并提高峰值尖瘦程度。",
        ],
        conclusion="本示例采用多方法对比并取较不利成果作为推荐值，成果链可继续传递至 4-01 水面线、STR 系列涉河专项校核。", 
    )
    figures = [
        ("瞬时单位线法洪水过程线", _series_svg("瞬时单位线法洪水过程线", unit_result.get("series", []))),
        ("推理公式法洪水过程线", _series_svg("推理公式法洪水过程线", rational_result.get("series", []))),
    ]
    return _book("CB-02", "设计洪水计算书", project_name, content, figures, evidence)


def _build_cb_03(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-03 20260804
    # Generates the detailed seven-chapter water-surface profile calculation book for 4-01.
    result = flood_mainline.calculate_wsl_01(SAMPLE_PARAMS["4-01"])
    rows = _flatten_result_tables(result)
    evidence = [
        ParameterEvidence("设计流量 Q", "40.00", "m³/s", "设计洪水成果", "CB-02 采用值或用户输入", "作为水面线推求控制流量。"),
        ParameterEvidence("下游控制水位", "103.50", "m", "实测/设计资料", "下游控制断面水位资料", "亚临界流按下游控制向上游推算。"),
        ParameterEvidence("糙率 n", "0.035", "", "规范查表", "天然河道糙率取值表", "河床较顺直、岸坡一般植被，按中等糙率取用。"),
        ParameterEvidence("断面地形", "2 个断面", "", "实测", "横断面测量成果", "用于计算过水面积、湿周、水力半径。"),
        ParameterEvidence("局部损失系数", "收缩 0.10 / 扩散 0.30", "", "规范查表", "桥涵及河道水力计算经验系数", "考虑断面变化引起的局部能量损失。"),
    ]
    content = _seven_chapter_content(
        title="CB-03 设计洪水位与水面线计算书",
        task="本计算书用于沿河道断面推求设计洪水位和水面线成果，为桥前壅水、净空校核、堤顶高程和影响评价提供水位边界。",
        method="采用能量方程/HEC 标准步法。亚临界流从下游控制水位向上游逐段试算，超临界流则反向控制。",
        evidence=evidence,
        steps=[
            ("断面水力要素", "由横断面地形和水位计算过水面积、湿周、水力半径、流速和弗汝德数。", "A=f(Z), R=A/P, V=Q/A", "Q=40.00 m³/s，断面地形点列代入", "得到各断面水力要素。"),
            ("能量方程", "相邻断面总能量差等于沿程损失和局部损失。", "E2=E1+hf+hj", "hf 按平均输水能力法，hj 按速度水头差乘损失系数", "逐段求解上游设计水位。"),
            ("流态判别", "弗汝德数用于判别控制条件是否合理。", "Fr=V/√(gD)", "计算断面平均水深 D 与流速 V", "Fr<1 时亚临界假定成立。"),
        ],
        result_rows=rows,
        analysis=[
            "本示例断面弗汝德数满足亚临界流控制条件，计算方向与下游水位控制一致。",
            "与曼宁均匀流估算相比，标准步法能体现断面变化和局部损失，适合涉河建筑物影响评价。",
            "敏感性分析：糙率 n 增加 10% 时沿程损失增大，上游水位抬升；流量增加 10% 时流速和速度水头增大，应联动复核壅水与净空。",
        ],
        conclusion="4-01 输出的沿程设计洪水位可作为阻水比、桥前壅水、冲刷、净空与基础埋深安全校核的统一水位基准。",
    )
    figures = [("沿程设计洪水位水面线", _series_svg("沿程设计洪水位水面线", result.get("series", [])))]
    return _book("CB-03", "设计洪水位与水面线计算书", project_name, content, figures, evidence)


def _build_cb_04(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-04 20260804
    # Generates the seven-chapter reservoir flood-routing calculation book for 5-01.
    return _single_program_book(
        cb_id="CB-04",
        title="调洪演算计算书",
        project_name=project_name,
        program_id="5-01",
        program_name="水库调洪演算",
        calculator=book_program_adapters.calculate_rsr_01,
        params={
            "inflow_hydrograph": "0,120;1,360;2,820;3,650;4,320;5,120",
            "elevation_storage_curve": "1200,1000000;1205,2200000;1210,4200000",
            "elevation_discharge_curve": "1200,30;1205,260;1210,850",
            "initial_elevation_m": 1200.0,
        },
        task="复核入库洪水经水库库容和泄洪建筑物调节后的出库过程、最高库水位和削峰效果。",
        method="采用水量平衡方程进行逐时段调洪演算，入库、出库和库容变化保持时段水量守恒。",
        analysis=[
            "入库洪峰经水库调蓄后出库过程应滞后且削峰，最高库水位应落在库容曲线有效范围内。",
            "若最高水位接近校核洪水位，应联动复核泄洪能力和坝顶超高。",
            "敏感性分析：入库洪峰增加 10% 或初始水位提高时，最高库水位和最大下泄流量均会抬升。",
        ],
        conclusion="调洪演算成果可作为泄洪能力、坝顶超高和下游防洪影响复核的水库边界条件。",
    )


def _build_cb_05(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-05 20260804
    # Generates the seven-chapter spillway and flood-discharge capacity calculation book for 5-02.
    return _single_program_book(
        cb_id="CB-05",
        title="泄洪能力复核计算书",
        project_name=project_name,
        program_id="5-02",
        program_name="泄洪能力复核",
        calculator=book_program_adapters.calculate_rsr_02,
        params={},
        task="复核水库泄洪建筑物在设计水位和校核水位下的过流能力，判断是否满足调洪与防洪安全要求。",
        method="复用软件内置泄洪能力计算程序，按堰流、孔流或泄槽水力关系分项计算并汇总。",
        analysis=[
            "泄洪能力应与调洪演算最大下泄流量相互闭合，不能只看单一建筑物额定流量。",
            "当过流能力不足时，应检查堰顶高程、闸门开度、进口收缩和下游淹没影响。",
            "敏感性分析：水头增加 10% 时泄量通常非线性增加，出口淹没加重时有效泄量会降低。",
        ],
        conclusion="泄洪能力复核成果用于判定泄洪建筑物是否满足调洪演算出流和水库防洪控制要求。",
    )


def _build_cb_06(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-06 20260804
    # Generates the seven-chapter obstruction-ratio calculation book for 3-15.
    return _single_program_book(
        cb_id="CB-06",
        title="阻水比计算书",
        project_name=project_name,
        program_id="3-15",
        program_name="阻水比/过水面积压缩比",
        calculator=river_special.calculate_str_01,
        params={"natural_area_m2": 260.0, "effective_area_m2": 232.0, "design_flow_m3s": 240.0, "natural_width_m": 80.0, "effective_width_m": 72.0},
        task="计算涉河建筑物占用河道后过水面积和有效河宽变化，判断阻水比是否处于可接受范围。",
        method="按天然断面和工程后有效过水断面对比，计算面积压缩比、宽度压缩比和流速变化。",
        analysis=[
            "阻水比越大，桥前壅水和局部流速增幅越明显，应与壅水、冲刷计算联合判定。",
            "面积压缩与河宽压缩结果若差异明显，应检查墩台布置、滩地占用和主槽偏流。",
            "敏感性分析：有效过水面积减少 10% 会直接提高压缩比，并可能放大后续壅水结果。",
        ],
        conclusion="阻水比成果是 3-16 壅水、3-13 冲刷和净空校核的基础几何控制指标。",
    )


def _build_cb_07(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-07 20260804
    # Generates the seven-chapter backwater calculation book for 3-16/3-14.
    result_main = river_special.calculate_str_02(
        {
            "design_flow_m3s": 240.0,
            "full_section_area_m2": 260.0,
            "natural_channel_velocity_ms": 1.8,
            "bridge_actual_area_m2": 232.0,
            "natural_water_surface_slope": 0.001,
            "obstruction_area_ratio_percent": 10.8,
        }
    )
    result_curve = river_special.calculate_str_06(
        {"max_backwater_height_m": 0.26, "backwater_length_m": 360.0, "design_water_level_m": 1202.0, "bed_slope": 0.001}
    )
    rows = _merge_result_rows([("桥前壅水", result_main), ("上游回水影响", result_curve)])
    evidence = _standard_evidence("3-16/3-14", "桥前壅水与上游回水影响", result_main, result_curve)
    content = _seven_chapter_content(
        title="CB-07 壅水计算书",
        task="计算桥前壅水高度和上游回水影响范围，为河道行洪影响评价、水面线修正和净空复核提供边界。",
        method="按规范经验公式计算桥前壅水高度，并依据最大壅水高度、河道比降和影响长度推求上游回水曲线。",
        evidence=evidence,
        steps=[
            ("阻水参数整理", "阻水比和有效过水面积反映工程对天然行洪断面的压缩程度。", "β=(A0-Ae)/A0×100%", "A0=260.00，Ae=232.00", "得到壅水计算控制阻水参数。"),
            ("桥前壅水计算", "壅水高度表示工程前后局部能量损失造成的水位抬高。", "ΔZ=f(Q,A,V,β,J)", "Q=240.00 m³/s，β=10.8%，J=0.001", "输出桥前最大壅水高度。"),
            ("回水曲线推求", "上游回水沿程逐渐衰减，用于判断影响范围和断面复核边界。", "Z(x)=Z0+ΔZ·衰减函数", "ΔZ=0.26m，影响长度=360m", "形成上游回水影响曲线。"),
        ],
        result_rows=rows,
        analysis=[
            "壅水成果应与阻水比、流速增幅和水面线成果相互校核，不能孤立采用。",
            "若壅水曲线影响至上游敏感断面，应在报告中增加断面水位增量表。",
            "敏感性分析：阻水比增加 10% 或天然水面比降减小时，壅水影响通常加大。",
        ],
        conclusion="壅水计算成果可直接传递到净空校核、防洪影响评价和上游回水影响范围判定；当项目或审查意见给出允许水位增量限值时，应以该限值进行满足/不满足判别。",
    )
    figures = [("壅水曲线", _series_svg("壅水曲线", result_curve.get("series", [])))]
    return _book("CB-07", "壅水计算书", project_name, content, figures, evidence)


def _build_cb_08(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-08 20260804
    # Generates the seven-chapter scour calculation book for 3-13.
    return _single_program_book(
        cb_id="CB-08",
        title="冲刷计算书",
        project_name=project_name,
        program_id="3-13",
        program_name="一般冲刷与局部冲刷",
        calculator=river_special.calculate_str_03,
        params={},
        task="计算涉河建筑物附近一般冲刷和局部冲刷深度，作为基础埋深和防护设计控制依据。",
        method="复用软件内置冲刷公式，分别计算河床一般冲刷、墩台局部冲刷和综合冲刷线。",
        analysis=[
            "冲刷深度应与设计洪水位、流速、河床粒径和墩台形态联动复核。",
            "当局部冲刷控制时，需重点核对桥墩形状系数、来流夹角和河床材料取值。",
            "敏感性分析：设计流速增加 10% 通常显著放大局部冲刷，应作为基础安全的重点复核项。",
        ],
        conclusion="冲刷成果是基础埋深安全校核和河床防护措施设计的直接输入。",
    )


def _build_cb_09(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-09 20260804
    # Generates the seven-chapter clearance check calculation book for 3-17.
    return _single_program_book(
        cb_id="CB-09",
        title="净空校核计算书",
        project_name=project_name,
        program_id="3-17",
        program_name="净空校核",
        calculator=river_special.calculate_str_04,
        params={"design_water_level_m": 1202.0, "backwater_height_m": 0.26, "safety_freeboard_m": 0.5, "girder_bottom_elevation_m": 1205.0},
        task="校核设计洪水位、壅水高度和安全超高叠加后是否低于梁底或控制结构底高程。",
        method="按控制水位加壅水和安全超高形成要求高程，与梁底/结构底高程比较得到净空富余量。",
        analysis=[
            "净空校核必须使用采用的设计洪水位和壅水成果，不能只按天然水位判断。",
            "若富余量为负，应调整结构高程或补充防洪影响论证。",
            "敏感性分析：设计水位或壅水高度增加 0.10m 将等量降低净空富余量。",
        ],
        conclusion="净空校核结论直接用于判断涉河建筑物行洪安全是否满足标准限值。",
    )


def _build_cb_10(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-10 20260804
    # Generates the seven-chapter foundation embedment safety calculation book for 3-12.
    return _single_program_book(
        cb_id="CB-10",
        title="基础埋深安全计算书",
        project_name=project_name,
        program_id="3-12",
        program_name="基础埋深安全",
        calculator=river_special.calculate_str_05,
        params={"bed_elevation_m": 1198.0, "general_scour_depth_m": 1.2, "local_scour_depth_m": 0.8, "foundation_bottom_elevation_m": 1193.0, "required_embedment_m": 1.5},
        task="校核一般冲刷和局部冲刷后基础底高程是否仍满足最小埋深安全要求。",
        method="先由河床高程扣除冲刷深度得到冲刷线，再与基础底高程比较富余埋深。",
        analysis=[
            "基础埋深应与冲刷成果采用同一设计频率和同一工况。",
            "若富余埋深不足，应调整基础底高程或增设防冲措施。",
            "敏感性分析：冲刷深度增加 10% 将直接降低富余埋深，局部冲刷控制时风险更集中。",
        ],
        conclusion="基础埋深安全成果用于判断涉河建筑物基础在设计洪水作用下是否满足抗冲安全。",
    )


def _build_cb_11(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-11 20260804
    # Generates the seven-chapter river evolution calculation book for RIV programs.
    return _single_program_book(
        cb_id="CB-11",
        title="河道演变计算书",
        project_name=project_name,
        program_id="3-09",
        program_name="河道演变分析",
        calculator=book_program_adapters.calculate_riv_01,
        params={},
        task="分析河道平面形态、纵横断面和冲淤趋势，为防洪影响评价和河势稳定性判断提供依据。",
        method="复用河道演变分析程序，对河相关系、稳定河宽、河床稳定性和冲淤判别成果进行归纳。",
        analysis=[
            "河道演变结论应与历史遥感、断面复测和水沙条件相互印证。",
            "若工程位于弯道、分汊或冲淤敏感河段，应提高河势稳定性评价权重。",
            "敏感性分析：坡降、粒径和流速变化会影响河床冲淤趋势，应结合实测资料复核。",
        ],
        conclusion="河道演变成果用于说明工程建设前后河势稳定性和防洪影响评价的背景条件。",
    )


def _build_cb_12(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-12 20260804
    # Generates the seven-chapter irrigation/channel/pump-station calculation book for IRR/CHN programs.
    return _single_program_book(
        cb_id="CB-12",
        title="渠道泵站计算书",
        project_name=project_name,
        program_id="2-01",
        program_name="泵站设计流量与扬程复核",
        calculator=book_program_adapters.calculate_irr_01,
        params={},
        task="复核灌溉泵站设计流量、扬程和渠道输水能力，判断工程供水能力是否满足灌溉制度要求。",
        method="复用灌溉泵站和渠道输水计算程序，按设计流量、净扬程、损失扬程和渠道过流能力分项复核。",
        analysis=[
            "泵站设计流量应与灌水率、控制面积和轮灌制度一致。",
            "扬程复核应区分净扬程、沿程损失和局部损失，避免只按水位差判断。",
            "敏感性分析：渠道糙率增加或输水距离增加时，损失扬程上升，泵站装机和能耗需同步复核。",
        ],
        conclusion="渠道泵站计算成果用于确定供水能力、设备选型和渠道断面是否满足灌溉工程需求。",
    )


def _build_cb_13(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-13 20260804
    # Generates the seven-chapter water-hammer calculation book for 6-03.
    return _single_program_book(
        cb_id="CB-13",
        title="水锤计算书",
        project_name=project_name,
        program_id="6-03",
        program_name="压力管道水锤",
        calculator=book_program_adapters.calculate_hyd_03,
        params={},
        task="计算压力管道在流量突变、阀门启闭或机组甩负荷条件下的水锤压力增量。",
        method="复用水电站压力管道水锤计算程序，按波速、流速变化和水头叠加关系进行复核。",
        analysis=[
            "水锤压力应与静水压力叠加后校核管道强度和附属设备安全。",
            "阀门关闭时间、管道弹性和水体可压缩性是控制水锤幅值的关键参数。",
            "敏感性分析：关闭时间缩短或流速增加会显著增大水锤压力，应作为运行控制重点。",
        ],
        conclusion="水锤计算成果用于压力管道强度复核、阀门启闭制度和水电站运行安全评价。",
    )


def _build_cb_14(project_name: str) -> CalcBookDocument:
    # @hook B9-CB-14 20260804
    # Generates the seven-chapter hydraulic-structures calculation book for HST programs.
    return _single_program_book(
        cb_id="CB-14",
        title="水工建筑物计算书",
        project_name=project_name,
        program_id="7-01",
        program_name="水闸过流能力",
        calculator=book_program_adapters.calculate_hst_01,
        params={},
        task="复核水闸、溢洪道、渡槽、隧洞等水工建筑物的过流能力和关键水力参数。",
        method="复用《水利大算盘》接入的水工建筑物计算程序，按建筑物类型选择相应堰流、孔流、明渠流或压力流公式。",
        analysis=[
            "水工建筑物计算应先明确流态、控制水头和淹没条件，再选择公式。",
            "当上下游水位关系改变时，需检查自由出流和淹没出流的转换。",
            "敏感性分析：水头、孔口宽度和流量系数变化会直接影响过流能力，应结合规范取值范围复核。",
        ],
        conclusion="水工建筑物计算成果可用于单体建筑物过流能力、尺寸复核和水力安全评价。",
    )


def _single_program_book(
    *,
    cb_id: str,
    title: str,
    project_name: str,
    program_id: str,
    program_name: str,
    calculator,
    params: dict[str, Any],
    task: str,
    method: str,
    analysis: list[str],
    conclusion: str,
) -> CalcBookDocument:
    result = calculator(params)
    evidence = _standard_evidence(program_id, program_name, result)
    rows = _flatten_result_tables(result)
    content = _seven_chapter_content(
        title=f"{cb_id} {title}",
        task=task,
        method=method,
        evidence=evidence,
        steps=_generic_steps(program_name, result, params),
        result_rows=rows,
        analysis=analysis,
        conclusion=conclusion,
    )
    figures = _figures_from_result(title, result)
    return _book(cb_id, title, project_name, content, figures, evidence)


def _standard_evidence(program_id: str, program_name: str, *results: dict[str, Any]) -> list[ParameterEvidence]:
    source = "；".join(str(result.get("source") or "软件内置公式模块") for result in results if result) or "软件内置公式模块"
    evidence = [
        ParameterEvidence("程序编号", program_id, "", "程序注册表", "program_registry.py", f"按 {program_name} 对应程序入口组织计算书。"),
        ParameterEvidence("公式来源", source, "", "软件既有公式模块", "对应 Python 计算模块", "计算书引擎只读取结构化结果，不重写公式。"),
        ParameterEvidence("工程工况", "现状/工程后", "", "设计资料", "工程方案与频率管理", "用于区分天然条件和工程布置后的复核成果。"),
        ParameterEvidence("设计频率", "P=1/2/5/10%", "", "规范取值", "防洪标准及项目等级确定的频率组合", "满足多频率并行复核和报告成果表要求。"),
    ]
    for result in results:
        for metric in result.get("metrics", []):
            source_type, source_ref, reason = _metric_evidence_source(program_id, str(metric.get("name") or ""), str(metric.get("note") or ""))
            evidence.append(
                ParameterEvidence(
                    str(metric.get("name") or "成果指标"),
                    _display_metric_value(metric.get("value")),
                    str(metric.get("unit") or ""),
                    source_type,
                    source_ref,
                    reason,
                )
            )
    return evidence


def _display_metric_value(value: Any) -> str:
    if value is None or value == "":
        return "—"
    return str(value)


def _metric_evidence_source(program_id: str, name: str, note: str = "") -> tuple[str, str, str]:
    """Classify parameter evidence so review tables distinguish inputs, standards and calculated outputs."""
    normalized = name.strip()

    if "公式" in normalized:
        return ("公式来源", "对应计算模块或规范公式", note or "记录计算采用的公式形式或程序公式来源，不作为成果指标。")

    if any(keyword in normalized for keyword in ("安全超高", "安全加高", "要求埋深", "允许", "限值", "控制标准")):
        return (
            "规范取值",
            "防洪标准、桥涵及涉河工程净空/基础安全控制要求",
            "按工程等级、建筑物类型和审查要求选取的安全控制值，不属于计算程序输出成果。",
        )

    if "壅水系数" in normalized or normalized in {"η", "糙率", "流量系数", "侧收缩系数", "淹没系数"}:
        return (
            "规范取值",
            "SL/T 808-2025 附录A及相关水力计算参数表",
            "按阻水形态、河段类型或水力条件查表选取，用于公式代入。",
        )

    if program_id == "5-01" and "入库洪峰" in normalized:
        return ("计算成果", "设计洪水过程线成果", "由设计洪水过程线读取，作为水库调洪演算的入库边界。")

    if program_id == "3-15":
        if "天然过水面积" in normalized:
            return ("实测资料", "河道天然断面测量成果", "由天然断面地形整理得到，作为阻水比计算基准。")
        if "建后有效过水面积" in normalized:
            return ("设计资料", "涉河建筑物布置图及桥孔/墩台尺寸资料", "由工程布置扣除阻水构件后整理得到。")

    if program_id in {"3-16", "3-16/3-14", "3-14"}:
        if "面积阻水比" in normalized:
            return ("计算成果", "3-15 阻水比计算成果", "作为壅水经验公式的上游控制参数。")
        if "最大壅水高度" in normalized or "壅水影响长度" in normalized or "上游影响长度" in normalized:
            return ("计算成果", "3-16/3-14 壅水计算成果", "由壅水公式或回水曲线推求得到，用于防洪影响范围判定。")
        if "控制断面水位" in normalized:
            return ("计算成果", "设计洪水位与壅水成果叠加", "作为上游回水影响曲线的控制水位。")

    if program_id == "3-17":
        if "设计洪水位" in normalized:
            return ("计算成果", "4-01 设计洪水位/水面线成果", "作为净空校核的基础控制水位。")
        if "壅水高度" in normalized:
            return ("计算成果", "3-16/3-14 壅水成果", "作为工程影响引起的水位增量输入。")
        if "实际梁底" in normalized:
            return ("设计资料", "桥梁或涉河建筑物结构设计图", "由结构底高程或梁底高程资料读取。")

    if program_id == "3-12":
        if "现状河床高程" in normalized:
            return ("实测资料", "河道断面测量成果", "作为冲刷线和基础埋深校核的地形基准。")
        if "一般冲刷深度" in normalized or "局部冲刷深度" in normalized:
            return ("计算成果", "3-13 冲刷计算成果", "作为基础埋深安全校核的上游输入。")
        if "基础底高程" in normalized:
            return ("设计资料", "基础设计图或施工图", "由基础底面设计高程资料读取。")

    if any(keyword in normalized for keyword in ("高程", "底宽", "边坡", "断面", "水头", "闸门", "管径", "壁厚")) and not any(
        keyword in normalized for keyword in ("采用", "计算", "所需", "控制", "富余", "总净宽")
    ):
        return ("设计资料", "工程布置、断面或设备设计资料", "作为计算输入或复核边界条件。")

    return ("计算成果", program_id, note or "由对应计算程序输出，作为成果判别指标。")


def _generic_steps(program_name: str, result: dict[str, Any], params: dict[str, Any]) -> list[tuple[str, str, str, str, str]]:
    metrics = result.get("metrics", [])
    first_metric = metrics[0] if metrics else {"name": "主要成果", "value": "见成果表", "unit": ""}
    param_text = "，".join(f"{key}={value}" for key, value in list(params.items())[:6]) or "采用程序默认示例参数"
    return [
        (
            "输入资料整理",
            f"把 {program_name} 所需的流量、水位、断面、结构尺寸或运行参数统一为结构化输入。",
            "Input = {设计资料, 规范查表参数, 工程布置参数}",
            param_text,
            "完成参数合法性检查和单位统一。",
        ),
        (
            "公式模块计算",
            "调用软件既有 Python 公式模块，保持与界面计算和例题矩阵同一套算法。",
            "Result = calculate(program_id, params)",
            f"program_id 对应 {program_name}",
            f"输出 {first_metric.get('name')}={first_metric.get('value')}{first_metric.get('unit','')}",
        ),
        (
            "成果判别与传递",
            "把关键结果与标准限值、上游成果或下游依赖关系比较，形成可写入报告的结论。",
            "Margin = 控制值 - 要求值；或 Ratio = 工程后/现状",
            "按成果表逐项读取控制指标",
            "形成满足/不满足、需复核或接入下游程序的判断。",
        ),
    ]


def _merge_result_rows(results: list[tuple[str, dict[str, Any]]]) -> list[list[Any]]:
    rows: list[list[Any]] = [["项目", "数值", "单位", "说明"]]
    for title, result in results:
        rows.append([title, "", "", ""])
        for row in _flatten_result_tables(result)[1:]:
            rows.append(row)
    return rows


def _figures_from_result(title: str, result: dict[str, Any]) -> list[tuple[str, str]]:
    figures: list[tuple[str, str]] = []
    if result.get("series"):
        figures.append((f"{title}过程线", _series_svg(f"{title}过程线", result.get("series", []))))
    if result.get("curve_points"):
        points = []
        for point in result.get("curve_points", []):
            try:
                points.append({"x": float(point.get("distance_m", 0.0)), "y": float(point.get("water_level_m", 0.0))})
            except (TypeError, ValueError):
                continue
        if points:
            figures.append((f"{title}曲线", _svg_polyline(f"{title}曲线", points, "distance_m", "water_level_m")))
    return figures


def _seven_chapter_content(
    *,
    title: str,
    task: str,
    method: str,
    evidence: list[ParameterEvidence],
    steps: list[tuple[str, str, str, str, str]],
    result_rows: list[list[Any]],
    analysis: list[str],
    conclusion: str,
) -> str:
    lines: list[str] = [
        title,
        "",
        "一、计算任务与工况说明",
        task,
        "本次按现状工况、工程后工况和常用设计频率成果组织资料，计算结果均保留来源程序、参数版本和成果用途，便于报告复核。",
        "",
        "二、计算方法与依据",
        method,
        "公式来源按软件程序登记表、现行水利水电工程设计洪水计算资料、暴雨洪水图集以及涉河专题常用水力计算方法进行归档；规范全文不复制，仅记录公式、适用范围和取值依据。",
        "",
        "三、基本资料与参数选取",
        "| 参数 | 取值 | 单位 | 来源 | 依据条文/资料 | 取值理由 |",
        "|---|---:|---|---|---|---|",
    ]
    for item in evidence:
        lines.append(f"| {item.name} | {item.value} | {item.unit} | {item.source} | {item.clause} | {item.reason} |")
    lines.extend(["", "四、计算过程"])
    for index, (name, meaning, formula, substitution, intermediate) in enumerate(steps, 1):
        lines.extend(
            [
                f"{index}. {name}",
                f"物理意义：{meaning}",
                f"公式出处与形式：{formula}",
                f"代入值：{substitution}",
                f"中间结果：{intermediate}",
            ]
        )
    lines.extend(["", "五、计算结果与成果表"])
    lines.extend(_markdown_table(result_rows))
    lines.extend(["", "六、成果合理性分析"])
    for item in analysis:
        lines.append(f"- {item}")
    lines.extend(
        [
            "- 与邻近站或地区经验对比时，应优先检查同流域、相近面积、相近下垫面条件，避免跨区直接套用。",
            "- 关键参数 ±10% 敏感性已纳入说明，正式工程可在成果审查前补充项目实测资料。",
            "",
            "七、结论与建议",
            conclusion,
            "若后续上游参数重算，依赖图会把本计算书相关成果标记为数据过期，需重新生成正式稿。",
        ]
    )
    return "\n".join(lines)


def _book(
    cb_id: str,
    title: str,
    project_name: str,
    content: str,
    figures: list[tuple[str, str]],
    evidence: list[ParameterEvidence],
) -> CalcBookDocument:
    word_count = len(re.findall(r"[\u4e00-\u9fff]|[A-Za-z0-9]+", content))
    page_estimate = max(2, (word_count // 850) + len(figures) + 1)
    sourced = sum(1 for item in evidence if item.source and item.clause and item.reason)
    return CalcBookDocument(cb_id, title, project_name, content, figures, word_count, page_estimate, len(evidence), sourced)


def _flatten_result_tables(result: dict[str, Any]) -> list[list[Any]]:
    rows: list[list[Any]] = [["项目", "数值", "单位", "说明"]]
    for item in result.get("metrics", []):
        rows.append([item.get("name", ""), item.get("value", ""), item.get("unit", ""), item.get("note", "")])
    for table in result.get("tables", [])[:2]:
        rows.append([table.get("title", "成果表"), "", "", ""])
        for item in table.get("rows", [])[:18]:
            rows.append([item.get("name", ""), item.get("value", ""), item.get("unit", ""), item.get("note", "")])
    return rows


def _markdown_table(rows: list[list[Any]]) -> list[str]:
    if not rows:
        return []
    header = [str(item) for item in rows[0]]
    lines = ["| " + " | ".join(header) + " |", "|" + "|".join("---" for _ in header) + "|"]
    for row in rows[1:]:
        normalized = [str(item) for item in row] + [""] * (len(header) - len(row))
        lines.append("| " + " | ".join(normalized[: len(header)]) + " |")
    return lines


def _metric_map(metrics: list[dict[str, Any]]) -> dict[str, float]:
    values: dict[str, float] = {}
    for item in metrics:
        key = str(item.get("name", "")).replace("年", "").strip()
        try:
            values[key] = float(item.get("value", 0.0))
        except (TypeError, ValueError):
            values[key] = 0.0
    return values


def _numeric_values(rows: list[list[Any]]) -> list[float]:
    values: list[float] = []
    for row in rows:
        try:
            values.append(float(row[1]))
        except (IndexError, TypeError, ValueError):
            continue
    return values or [0.0]


def _fmt(value: Any) -> str:
    try:
        return f"{float(value):.2f}"
    except (TypeError, ValueError):
        return str(value)


def _safe_filename(value: str) -> str:
    return re.sub(r'[\\/:*?"<>|]+', "_", value)


def _line_svg(title: str, xs: list[float], ys: list[float]) -> str:
    if not ys:
        ys = [0.0]
    if len(xs) != len(ys):
        xs = list(range(1, len(ys) + 1))
    points = [{"x": x, "y": y} for x, y in zip(xs, ys)]
    return _svg_polyline(title, points, "频率", "成果")


def _series_svg(title: str, series_list: list[dict[str, Any]]) -> str:
    for series in series_list:
        points = series.get("points") or []
        x_key = series.get("xKey") or "time_h"
        y_key = series.get("yKey") or "discharge_m3s"
        clean = []
        for point in points:
            try:
                clean.append({"x": float(point[x_key]), "y": float(point[y_key])})
            except (KeyError, TypeError, ValueError):
                continue
        if clean:
            return _svg_polyline(title, clean[:80], str(x_key), str(y_key))
    return _svg_polyline(title, [{"x": 0.0, "y": 0.0}, {"x": 1.0, "y": 0.0}], "x", "y")


def _svg_polyline(title: str, points: list[dict[str, float]], x_label: str, y_label: str) -> str:
    width, height, pad = 760, 360, 54
    xs = [point["x"] for point in points]
    ys = [point["y"] for point in points]
    min_x, max_x = min(xs), max(xs)
    min_y, max_y = min(ys), max(ys)
    range_x = max(max_x - min_x, 1e-9)
    range_y = max(max_y - min_y, 1e-9)

    def sx(value: float) -> float:
        return pad + (value - min_x) / range_x * (width - 2 * pad)

    def sy(value: float) -> float:
        return height - pad - (value - min_y) / range_y * (height - 2 * pad)

    poly = " ".join(f"{sx(point['x']):.1f},{sy(point['y']):.1f}" for point in points)
    return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}" font-family="Times New Roman, KaiTi, 楷体, serif">
<rect width="{width}" height="{height}" fill="#ffffff"/>
<text x="{width / 2}" y="30" text-anchor="middle" font-size="20" font-family="Times New Roman, KaiTi, 楷体, serif" font-weight="700" fill="#102A43">{title}</text>
<g stroke="#D9E2EC" stroke-width="1">
<line x1="{pad}" y1="{pad}" x2="{width - pad}" y2="{pad}"/>
<line x1="{pad}" y1="{height / 2}" x2="{width - pad}" y2="{height / 2}"/>
<line x1="{pad}" y1="{height - pad}" x2="{width - pad}" y2="{height - pad}"/>
</g>
<line x1="{pad}" y1="{height - pad}" x2="{width - pad}" y2="{height - pad}" stroke="#486581" stroke-width="1.5"/>
<line x1="{pad}" y1="{pad}" x2="{pad}" y2="{height - pad}" stroke="#486581" stroke-width="1.5"/>
<polyline points="{poly}" fill="none" stroke="#0A5382" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="{pad}" y="{pad - 12}" font-size="13" fill="#486581">{y_label}</text>
<text x="{width - pad - 60}" y="{height - 16}" font-size="13" fill="#486581">{x_label}</text>
<text x="{pad}" y="{height - 16}" font-size="12" fill="#829AB1">{min_x:.2f}</text>
<text x="{width - pad - 44}" y="{height - 16}" font-size="12" fill="#829AB1">{max_x:.2f}</text>
</svg>"""
