from __future__ import annotations

import math
from typing import Any

from .backwater import BackwaterInput, calculate_backwater
from .core import HydrologyError, _require_positive, _safe_round
from . import bridge_opening, river_evolution, wave
from .scour import (
    SCOUR_METHOD_MAP,
    SCOUR_METHODS,
    ScourCalculationInput,
    calculate_scour,
    parse_key_value_parameters,
)


G = 9.81


def _num(params: dict[str, Any], key: str, default: float | None = None) -> float:
    value = params.get(key, default)
    if value in (None, ""):
        raise HydrologyError(f"缺少参数：{key}")
    try:
        number = float(value)
    except (TypeError, ValueError) as exc:
        raise HydrologyError(f"{key} 必须是数字") from exc
    if not math.isfinite(number):
        raise HydrologyError(f"{key} 必须是有限数字")
    return number


def _positive(params: dict[str, Any], key: str, default: float | None = None) -> float:
    return _require_positive(key, _num(params, key, default))


def _choice(params: dict[str, Any], key: str, default: str) -> str:
    value = str(params.get(key, default)).strip()
    return value or default


def _status(pass_check: bool) -> str:
    return "满足" if pass_check else "不满足"


def _metric(name: str, value: Any, unit: str = "", note: str = "") -> dict[str, Any]:
    return {"name": name, "value": value, "unit": unit, "note": note}


def _table(title: str, rows: list[dict[str, Any]]) -> dict[str, Any]:
    return {"title": title, "rows": rows}


def _book(title: str, source: str, inputs: list[dict[str, Any]], outputs: list[dict[str, Any]], formulas: list[str]) -> str:
    lines = [
        title,
        "",
        "一、公式依据",
        source,
        "",
        "二、输入参数",
    ]
    for row in inputs:
        unit = f" {row.get('unit', '')}" if row.get("unit") else ""
        note = f"（{row.get('note')}）" if row.get("note") else ""
        lines.append(f"{row['name']} = {row['value']}{unit}{note}")
    lines.extend(["", "三、计算公式"])
    lines.extend(formulas)
    lines.extend(["", "四、计算结果"])
    for row in outputs:
        unit = f" {row.get('unit', '')}" if row.get("unit") else ""
        note = f"（{row.get('note')}）" if row.get("note") else ""
        lines.append(f"{row['name']} = {row['value']}{unit}{note}")
    return "\n".join(lines)


def _curve_points(max_height: float, length: float, base_level: float, bed_slope: float, steps: int) -> list[dict[str, float]]:
    if steps < 2:
        steps = 2
    if length <= 0 or max_height <= 0:
        return [{"distance_m": 0.0, "backwater_m": 0.0, "water_level_m": _safe_round(base_level, 4)}]
    points: list[dict[str, float]] = []
    for index in range(steps + 1):
        ratio = index / steps
        x = length * ratio
        # Smooth attenuation keeps the downstream value equal to max_height and approaches zero upstream.
        height = max_height * (1 - ratio) ** 2
        level = base_level + height - bed_slope * x
        points.append(
            {
                "distance_m": _safe_round(x, 2),
                "backwater_m": _safe_round(height, 4),
                "water_level_m": _safe_round(level, 4),
            }
        )
    return points


# @hook UI3-1 3-15 20260804
# Computes bridge/opening obstruction and hydraulic compression from before/after effective flow area.
def calculate_str_01(params: dict[str, Any]) -> dict[str, Any]:
    natural_area = _positive(params, "natural_area_m2")
    effective_area = _positive(params, "effective_area_m2")
    design_flow = _positive(params, "design_flow_m3s")
    if effective_area > natural_area:
        raise HydrologyError("建后有效过水面积不能大于天然过水面积")
    natural_width = _positive(params, "natural_width_m", 1.0)
    effective_width = _positive(params, "effective_width_m", natural_width)
    blocked_flow_ratio = max(0.0, _num(params, "blocked_flow_ratio_percent", 0.0))

    blocked_area = natural_area - effective_area
    area_ratio = blocked_area / natural_area * 100.0
    compression_ratio = natural_area / effective_area
    width_ratio = max(0.0, (natural_width - effective_width) / natural_width * 100.0)
    natural_velocity = design_flow / natural_area
    bridge_velocity = design_flow / effective_area

    outputs = [
        _metric("天然过水面积", _safe_round(natural_area, 3), "m²"),
        _metric("建后有效过水面积", _safe_round(effective_area, 3), "m²"),
        _metric("阻水面积", _safe_round(blocked_area, 3), "m²"),
        _metric("面积阻水比", _safe_round(area_ratio, 2), "%"),
        _metric("过水面积压缩比", _safe_round(compression_ratio, 4), ""),
        _metric("桥孔宽度压缩率", _safe_round(width_ratio, 2), "%"),
        _metric("天然平均流速", _safe_round(natural_velocity, 3), "m/s"),
        _metric("桥下平均流速", _safe_round(bridge_velocity, 3), "m/s"),
        _metric("河滩路堤阻挡流量比", _safe_round(blocked_flow_ratio, 2), "%"),
    ]
    source = "涉河建设项目洪水影响评价常用断面复核方法；面积阻水比按建前、建后过水面积差计算。"
    formulas = [
        "阻水面积 ΔA = A0 - A1",
        "面积阻水比 ηA = ΔA / A0 × 100%",
        "过水面积压缩比 K = A0 / A1",
        "平均流速 V = Q / A",
    ]
    return {
        "program_id": "3-15",
        "title": "阻水比/过水面积压缩比计算",
        "source": source,
        "metrics": outputs,
        "tables": [_table("阻水与水力压缩复核表", outputs)],
        "calculation_book_text": _book("阻水比/过水面积压缩比计算书", source, [
            _metric("天然过水面积A0", natural_area, "m²"),
            _metric("建后有效过水面积A1", effective_area, "m²"),
            _metric("设计流量Q", design_flow, "m³/s"),
            _metric("天然河宽B0", natural_width, "m"),
            _metric("有效桥孔宽B1", effective_width, "m"),
        ], outputs, formulas),
    }


# @hook UI3-1 3-16 20260804
# Reuses backwater.py for bridge-front backwater height and generates a report-friendly curve.
def calculate_str_02(params: dict[str, Any]) -> dict[str, Any]:
    result = calculate_backwater(
        BackwaterInput(
            design_flow_m3s=_positive(params, "design_flow_m3s"),
            full_section_area_m2=_positive(params, "full_section_area_m2"),
            natural_channel_velocity_ms=_positive(params, "natural_channel_velocity_ms"),
            bridge_actual_area_m2=_positive(params, "bridge_actual_area_m2"),
            flow_angle_deg=_num(params, "flow_angle_deg", 0.0),
            soil_type=_choice(params, "soil_type", "中等土"),
            obstruction_area_ratio_percent=_num(params, "obstruction_area_ratio_percent", 0.0),
            natural_water_surface_slope=_positive(params, "natural_water_surface_slope"),
            blocked_flow_ratio_percent=_num(params, "blocked_flow_ratio_percent", _num(params, "obstruction_area_ratio_percent", 0.0)),
            eta_manual=_num(params, "eta_manual", 0.0) or None,
            eta_source_label=str(params.get("eta_source_label", "")).strip(),
        )
    )
    design_level = _num(params, "design_water_level_m", 0.0)
    curve = _curve_points(
        result.max_backwater_height_m,
        result.backwater_curve_length_m,
        design_level,
        _num(params, "bed_slope", result.max_backwater_height_m / max(result.backwater_curve_length_m, 1.0)),
        int(_num(params, "curve_steps", 12)),
    )
    outputs = [
        _metric("最大壅水高度", result.max_backwater_height_m, "m"),
        _metric("壅水影响长度", result.backwater_curve_length_m, "m"),
        _metric("桥下平均流速", result.bridge_velocity_vm_ms, "m/s"),
        _metric("平均流速V", result.average_velocity_v_ms, "m/s"),
        _metric("壅水系数η", result.eta, "", result.eta_source),
        _metric("面积阻水比", result.obstruction_area_ratio_percent, "%"),
    ]
    source = "SL/T 808-2025 涉河建设项目洪水影响评价导则附录A；ΔZₘ=η(Vₘ²-V²)，Lᵧ=2ΔZₘ/I₀。"
    return {
        "program_id": "3-16",
        "title": "桥前壅水计算",
        "source": source,
        "metrics": outputs,
        "tables": [
            _table("桥前壅水成果表", outputs),
            _table("壅水曲线点表", [_metric(f"{p['distance_m']} m", p["water_level_m"], "m", f"壅水 {p['backwater_m']} m") for p in curve]),
        ],
        "curve_points": curve,
        "max_backwater_height_m": result.max_backwater_height_m,
        "backwater_curve_length_m": result.backwater_curve_length_m,
        "calculation_book_text": _book("桥前壅水计算书", source, [
            _metric("设计流量Qp", result.design_flow_m3s, "m³/s"),
            _metric("全河过水面积ω", result.design_bridge_area_wx_m2, "m²", "按Qp/(Vp·cosα)换算"),
            _metric("建后有效面积ωg", result.actual_bridge_area_wg_m2, "m²"),
            _metric("天然水面坡度I0", _num(params, "natural_water_surface_slope"), ""),
        ], outputs, [result.formula_note]),
    }


# @hook UI3-1 3-13 20260804
# Reuses scour.py for general and local bridge scour and summarizes the controlling scour line.
def calculate_str_03(params: dict[str, Any]) -> dict[str, Any]:
    bed_elevation = _num(params, "bed_elevation_m", 0.0)
    general_method = _choice(params, "general_method_key", "jtg_64_2_nonc_simplified")
    local_method = _choice(params, "local_method_key", "jtg_65_nonc_pier_auto")

    general_params = {
        "Qp": _positive(params, "Qp", 650.0),
        "Qc": _positive(params, "Qc", 500.0),
        "Qt1": _positive(params, "Qt1", 150.0),
        "Bc": _positive(params, "Bc", 100.0),
        "Bcg": _positive(params, "Bcg", 80.0),
        "lambda": _num(params, "lambda", 0.05),
        "mu": _positive(params, "mu", 0.97),
        "hcm": _positive(params, "hcm", 5.0),
        "Bz": _positive(params, "Bz", 100.0),
        "Hz": _positive(params, "Hz", 3.0),
    }
    local_params = {
        "Kxi": _positive(params, "Kxi", 1.0),
        "B1": _positive(params, "B1", 1.5),
        "v": _positive(params, "v", 2.5),
        "hp": _positive(params, "hp", general_params["hcm"]),
        "d": _positive(params, "d", 0.02),
    }
    general = calculate_scour(ScourCalculationInput(general_method, general_params))
    local = calculate_scour(ScourCalculationInput(local_method, local_params))
    general_depth = max(0.0, general.result_value - general_params["hcm"]) if general.result_name.endswith("hp") else general.result_value
    local_depth = max(0.0, local.result_value)
    total_depth = general_depth + local_depth
    scour_line = bed_elevation - total_depth
    outputs = [
        _metric("一般冲刷深度", _safe_round(general_depth, 3), "m", general.method_name),
        _metric("局部冲刷深度", _safe_round(local_depth, 3), "m", local.method_name),
        _metric("控制冲刷深度", _safe_round(total_depth, 3), "m"),
        _metric("冲刷线高程", _safe_round(scour_line, 3), "m"),
    ]
    source = f"{general.source}；{local.source}。"
    formulas = [general.formula, local.formula, "冲刷线高程 = 现状河床高程 - 一般冲刷深度 - 局部冲刷深度"]
    return {
        "program_id": "3-13",
        "title": "一般冲刷 + 局部冲刷计算",
        "source": source,
        "metrics": outputs,
        "tables": [
            _table("冲刷综合成果表", outputs),
            _table("一般冲刷中间参数", [_metric(name, value, "", note) for name, value, note in general.rows]),
            _table("局部冲刷中间参数", [_metric(name, value, "", note) for name, value, note in local.rows]),
        ],
        "general_scour_depth_m": _safe_round(general_depth, 3),
        "local_scour_depth_m": _safe_round(local_depth, 3),
        "scour_line_elevation_m": _safe_round(scour_line, 3),
        "calculation_book_text": _book("一般冲刷与局部冲刷计算书", source, [
            _metric("现状河床高程", bed_elevation, "m"),
            _metric("一般冲刷公式", general.method_name),
            _metric("局部冲刷公式", local.method_name),
        ], outputs, formulas),
    }


# Restored from the pre-book desktop program.  The later Web catalogue kept
# backwater and scour-line tools but accidentally lost this independent river
# regime/stability/erosion-deposition analysis entry.
def calculate_str_07(params: dict[str, Any]) -> dict[str, Any]:
    method = _choice(params, "method", river_evolution.RIVER_EVOLUTION_METHODS[0])
    if method not in river_evolution.RIVER_EVOLUTION_METHODS:
        raise HydrologyError("请选择有效的河道演变分析方法")
    data = river_evolution.RiverEvolutionInput(
        method=method,
        bankfull_width_b=_num(params, "bankfull_width_b", 80.0),
        average_depth_h=_num(params, "average_depth_h", 3.0),
        sediment_d=_num(params, "sediment_d", 1.0),
        formative_flow_q=_num(params, "formative_flow_q", 500.0),
        slope_j=_num(params, "slope_j", 0.001),
        stable_width_a=_num(params, "stable_width_a", 1.0),
        low_water_width_b=_num(params, "low_water_width_b", 20.0),
        longitudinal_phi_h1=_num(params, "longitudinal_phi_h1", 1.0),
        lateral_phi_b1=_num(params, "lateral_phi_b1", 0.2),
        inlet_velocity_v0=_num(params, "inlet_velocity_v0", 2.0),
        outlet_velocity_vi=_num(params, "outlet_velocity_vi", 2.0),
        inlet_depth_h0=_num(params, "inlet_depth_h0", 3.0),
        outlet_depth_hi=_num(params, "outlet_depth_hi", 3.0),
    )
    result = river_evolution.calculate_river_evolution(data)
    method_fields = {
        "河相关系-格鲁什科夫宽深公式": ("bankfull_width_b", "average_depth_h"),
        "河相关系-量纲分析公式": ("bankfull_width_b", "sediment_d", "average_depth_h"),
        "稳定河宽-阿尔图宁公式": ("formative_flow_q", "slope_j", "stable_width_a"),
        "纵向稳定系数-希尔兹数倒数法": ("sediment_d", "average_depth_h", "slope_j"),
        "纵向稳定系数-洛赫庆系数法": ("sediment_d", "slope_j"),
        "横向稳定系数-方法一": ("formative_flow_q", "slope_j", "bankfull_width_b"),
        "横向稳定系数-方法二": ("low_water_width_b", "bankfull_width_b"),
        "综合稳定系数": ("longitudinal_phi_h1", "lateral_phi_b1"),
        "冲淤判别系数": ("inlet_velocity_v0", "outlet_velocity_vi", "inlet_depth_h0", "outlet_depth_hi"),
    }
    labels = {
        "bankfull_width_b": ("平滩水面宽 B", "m"), "average_depth_h": ("平均水深 h", "m"),
        "sediment_d": ("床沙平均粒径 d", "mm"), "formative_flow_q": ("造床/平滩流量 Q", "m³/s"),
        "slope_j": ("河道综合比降 J", "m/m"), "stable_width_a": ("稳定河宽系数 A", ""),
        "low_water_width_b": ("枯水河宽 b", "m"), "longitudinal_phi_h1": ("纵向稳定系数 φh1", ""),
        "lateral_phi_b1": ("横向稳定系数 φb1", ""), "inlet_velocity_v0": ("进口流速 v0", "m/s"),
        "outlet_velocity_vi": ("出口流速 vi", "m/s"), "inlet_depth_h0": ("进口水深 h0", "m"),
        "outlet_depth_hi": ("出口水深 hi", "m"),
    }
    inputs = [_metric(labels[key][0], getattr(data, key), labels[key][1]) for key in method_fields[method]]
    outputs = [
        _metric(result.value_name, _safe_round(result.value, 6), result.unit),
        _metric("判别结论", result.judgement or "指标计算完成", ""),
        _metric("采用说明", result.note or "按所选历史程序公式计算", ""),
    ]
    catalog = [
        _metric(name, "可选", "", "独立入口；按资料条件选择，不以一种经验关系替代全部河势分析")
        for name in river_evolution.RIVER_EVOLUTION_METHODS
    ]
    source = "恢复自导入《水利大算盘》前的桌面版河道演变计算程序；经验指标须结合历年断面、遥感、泥沙与现场调查复核。"
    return {
        "program_id": "3-23",
        "title": "河道演变分析",
        "source": source,
        "formula": result.formula,
        "input_rows": inputs,
        "result_rows": outputs,
        "metrics": outputs,
        "tables": [
            _table("当前河道演变方法参数与成果", [*inputs, *outputs]),
            _table("历史河道演变方法清单", catalog),
        ],
        "calculation_book_text": _book("河道演变分析计算书", source, inputs, outputs, [result.formula]),
    }


# Restores the old "冲刷计算" method selector instead of limiting the Web
# client to one general-scour plus one local-scour combined case.
def calculate_str_08(params: dict[str, Any]) -> dict[str, Any]:
    method_key = _choice(params, "method_key", "jtg_64_2_nonc_simplified")
    method = SCOUR_METHOD_MAP.get(method_key)
    if method is None:
        raise HydrologyError("请选择有效的规范分项冲刷公式")
    raw_text = str(params.get("method_parameters", "")).strip()
    if not raw_text:
        raise HydrologyError("请按所选公式逐行填写参数，格式为 参数名=数值")
    submitted = parse_key_value_parameters(raw_text)
    required = [key for key, _description, _default in method.parameters]
    missing = [key for key in required if key not in submitted]
    if missing:
        raise HydrologyError(f"所选公式缺少参数：{', '.join(missing)}")
    result = calculate_scour(ScourCalculationInput(method_key, submitted))
    inputs = [
        _metric(key, submitted[key], "", description)
        for key, description, _default in method.parameters
    ]
    intermediate = [_metric(name, value, "", note) for name, value, note in result.rows]
    outputs = [
        _metric(result.result_name, _safe_round(result.result_value, 6), result.unit, result.note),
        _metric("适用分类", result.category, ""),
        _metric("公式来源", result.source, ""),
    ]
    method_catalog = [
        _metric(item.name, item.source, "", item.category)
        for item in SCOUR_METHODS
    ]
    return {
        "program_id": "3-24",
        "title": "规范分项冲刷计算",
        "source": result.source,
        "formula": result.formula,
        "input_rows": inputs,
        "result_rows": outputs,
        "metrics": outputs,
        "tables": [
            _table("所选冲刷公式参数代入表", inputs),
            _table("中间参数与分支判别", intermediate),
            _table("规范分项冲刷公式清单", method_catalog),
        ],
        "calculation_book_text": _book(
            "规范分项冲刷计算书", result.source, inputs, outputs,
            [result.formula, "中间参数、自动分支和适用条件见随表输出；不得以默认参数替代本工程水文、地勘和断面资料。"],
        ),
    }


# Restored from the final pre-Water-Abacus desktop module. The calculation was
# still present in bridge_opening.py, but had no Web registry entry or page.
def calculate_str_09(params: dict[str, Any]) -> dict[str, Any]:
    method = _choice(params, "method", "开阔、顺直微弯河段")
    if method not in bridge_opening.BRIDGE_OPENING_METHODS:
        raise HydrologyError("请选择有效的桥孔计算河段类型")
    data = bridge_opening.BridgeOpeningInput(
        method=method,
        design_flow_qp=_num(params, "design_flow_qp", 0.0),
        channel_flow_qc=_num(params, "channel_flow_qc", 0.0),
        channel_width_bc=_num(params, "channel_width_bc", 0.0),
        average_unit_discharge_qc=_num(params, "average_unit_discharge_qc", 0.0),
        floodplain_flow_qt=_num(params, "floodplain_flow_qt", 0.0),
        annual_mean_max_flow_qbar=_num(params, "annual_mean_max_flow_qbar", 0.0),
        # The desktop UI took dbar in mm and converted it before applying the
        # JTG formula, whose original unit is metres.
        bed_sediment_dbar=_num(params, "bed_sediment_dbar_mm", 0.0) / 1000.0,
        flood_flow_q2_percent=_num(params, "flood_flow_q2_percent", 0.0),
    )
    result = bridge_opening.calculate_bridge_opening(data)
    method_fields = {
        "峡谷河段": (),
        "开阔、顺直微弯河段": ("design_flow_qp", "channel_flow_qc", "channel_width_bc"),
        "分汊、弯曲河段": ("design_flow_qp", "channel_flow_qc", "channel_width_bc"),
        "滩、槽可分的不稳定河段": ("design_flow_qp", "channel_flow_qc", "channel_width_bc"),
        "宽滩河段": ("design_flow_qp", "channel_flow_qc", "floodplain_flow_qt", "average_unit_discharge_qc"),
        "滩、槽难分的不稳定河段": ("design_flow_qp", "annual_mean_max_flow_qbar", "bed_sediment_dbar_mm", "flood_flow_q2_percent"),
    }
    labels = {
        "design_flow_qp": ("设计流量 Qp", "m³/s"),
        "channel_flow_qc": ("河槽流量 Qc", "m³/s"),
        "channel_width_bc": ("河槽宽度 Bc", "m"),
        "average_unit_discharge_qc": ("河槽平均单宽流量 qc", "m³/(s·m)"),
        "floodplain_flow_qt": ("河滩流量 Qt", "m³/s"),
        "annual_mean_max_flow_qbar": ("年最大流量均值 Qbar", "m³/s"),
        "bed_sediment_dbar_mm": ("河床泥沙平均粒径 dbar", "mm"),
        "flood_flow_q2_percent": ("2% 洪水流量 Q2%", "m³/s"),
    }
    inputs = [_metric(labels[key][0], params.get(key), labels[key][1]) for key in method_fields[method]]
    outputs = [
        _metric("河段类型", result.method),
        _metric("桥孔最小净长度 Lj", "按河床地形布孔" if result.min_clear_length_lj is None else _safe_round(result.min_clear_length_lj, 3), "" if result.min_clear_length_lj is None else "m"),
    ]
    for name, value, unit in (
        ("河段系数 Kq", result.kq, ""),
        ("指数 n3", result.n3, ""),
        ("宽滩系数 beta", result.beta, ""),
        ("基本河宽 B0", result.basic_width_b0, "m"),
        ("频率系数 Cp", result.frequency_factor_cp, ""),
    ):
        if value is not None:
            outputs.append(_metric(name, _safe_round(value, 6), unit))
    outputs.append(_metric("采用说明", result.note, ""))
    source = "JTG C30 桥孔设计第 7.2.1 节；恢复自导入《水利大算盘》前的桥孔最小净长度程序。"
    return {
        "program_id": "3-25",
        "title": "桥孔最小净长度计算",
        "source": source,
        "formula": result.formula,
        "input_rows": inputs,
        "result_rows": outputs,
        "metrics": outputs,
        "tables": [_table("桥孔净长度参数与成果", [*inputs, *outputs])],
        "calculation_book_text": _book("桥孔最小净长度计算书", source, inputs, outputs, [result.formula]),
    }


# Restores the standalone wave-height tab from the final desktop water-level
# module. Wind setup remains a separate program below, matching the old UI.
def calculate_str_10(params: dict[str, Any]) -> dict[str, Any]:
    frequency_raw = params.get("cumulative_frequency_percent")
    duration_raw = params.get("wind_duration_h")
    frequency = None if frequency_raw in (None, "") else _positive(params, "cumulative_frequency_percent")
    duration = None if duration_raw in (None, "") else _positive(params, "wind_duration_h")
    wind_speed = _positive(params, "wind_speed_ms")
    fetch = _positive(params, "fetch_length_m")
    depth = _positive(params, "average_depth_m")
    wave_result = wave.calculate_wave_height(wave.WaveHeightInput(
        wind_speed_ms=wind_speed,
        fetch_length_m=fetch,
        average_depth_m=depth,
        gravity=_positive(params, "gravity", 9.81),
        height_factor=_positive(params, "height_factor", 1.0),
        cumulative_frequency_percent=frequency,
        wind_duration_h=duration,
    ))
    inputs = [
        _metric("计算风速 V", wind_speed, "m/s"), _metric("风区长度 F/D", fetch, "m"),
        _metric("平均水深 d/Hm", depth, "m"),
    ]
    outputs = [
        _metric("平均波高 H", wave_result.mean_wave_height_m, "m"),
        _metric("设计波高 Hp", wave_result.design_wave_height_m, "m", wave_result.height_factor_source),
        _metric("波浪周期 T", wave_result.mean_wave_period_s, "s"),
        _metric("波长 L", wave_result.wavelength_m, "m"),
        _metric("最小风时", wave_result.minimum_wind_duration_h, "h"),
    ]
    wave_rows = [_metric(name, value, unit) for name, value, unit in [
        ("无量纲水深 gd/V²", wave_result.dimensionless_depth, ""),
        ("无量纲吹程 gF/V²", wave_result.dimensionless_fetch, ""),
        ("平均波高/水深 H/d", wave_result.height_depth_ratio, ""),
        ("波陡 Hp/L", wave_result.wave_steepness, ""),
    ]]
    source = "GB 50286-2013 表 C.1.3 与莆田试验站波浪公式；恢复自历史水位计算模块。"
    formulas = [wave_result.formula_note]
    return {
        "program_id": "4-04",
        "title": "波浪高度计算",
        "source": source,
        "formula": "；".join(formulas),
        "input_rows": inputs,
        "result_rows": outputs,
        "metrics": outputs,
        "tables": [
            _table("波浪高度中间参数", wave_rows),
            _table("波浪高度成果", outputs),
        ],
        "calculation_book_text": _book("波浪高度计算书", source, inputs, outputs, formulas),
    }


# Kept independent from wave height because the historical professional module
# exposed wind setup as its own calculation page and project input set.
def calculate_str_11(params: dict[str, Any]) -> dict[str, Any]:
    wind_speed = _positive(params, "wind_speed_ms")
    fetch = _positive(params, "fetch_length_m")
    depth = _positive(params, "average_depth_m")
    base_level = _num(params, "base_water_level_m")
    angle = _num(params, "wind_angle_deg", 0.0)
    coefficient = _positive(params, "wind_stress_coefficient", 3.6e-6)
    gravity = _positive(params, "gravity", 9.81)
    result = wave.calculate_wind_setup(wave.WindSetupInput(
        base_water_level_m=base_level,
        wind_speed_ms=wind_speed,
        fetch_length_m=fetch,
        average_depth_m=depth,
        wind_angle_deg=angle,
        wind_stress_coefficient=coefficient,
        gravity=gravity,
    ))
    inputs = [
        _metric("静水位 Z0", base_level, "m"), _metric("计算风速 V", wind_speed, "m/s"),
        _metric("风区长度 D", fetch, "m"), _metric("平均水深 Hm", depth, "m"),
        _metric("风向夹角 beta", angle, "°"), _metric("风壅综合摩阻系数 k", coefficient, ""),
        _metric("重力加速度 g", gravity, "m/s²"),
    ]
    outputs = [
        _metric("风向折减 cos(beta)", result.direction_factor, ""),
        _metric("风壅增水 hz", result.wind_setup_m, "m"),
        _metric("叠加后水位 Z", result.adjusted_water_level_m, "m"),
    ]
    source = "风壅水面高度常用近似式；恢复自历史水位计算模块的独立风壅水位页签。"
    return {
        "program_id": "4-05",
        "title": "风壅水位计算",
        "source": source,
        "formula": result.formula_note,
        "input_rows": inputs,
        "result_rows": outputs,
        "metrics": outputs,
        "tables": [_table("风壅水位参数与成果", [*inputs, *outputs])],
        "calculation_book_text": _book("风壅水位计算书", source, inputs, outputs, [result.formula_note]),
    }


# @hook UI3-1 3-17 20260804
# Checks required bridge/structure clearance against design water level plus backwater and freeboard.
def calculate_str_04(params: dict[str, Any]) -> dict[str, Any]:
    design_level = _num(params, "design_water_level_m")
    backwater = max(0.0, _num(params, "backwater_height_m", 0.0))
    freeboard = max(0.0, _num(params, "safety_freeboard_m", 0.5))
    bottom = _num(params, "girder_bottom_elevation_m")
    control_level = design_level + backwater
    required = control_level + freeboard
    margin = bottom - required
    outputs = [
        _metric("设计洪水位", _safe_round(design_level, 3), "m"),
        _metric("壅水高度", _safe_round(backwater, 3), "m"),
        _metric("控制水位", _safe_round(control_level, 3), "m"),
        _metric("安全超高", _safe_round(freeboard, 3), "m"),
        _metric("所需梁底/结构底高程", _safe_round(required, 3), "m"),
        _metric("实际梁底/结构底高程", _safe_round(bottom, 3), "m"),
        _metric("净空富余", _safe_round(margin, 3), "m", _status(margin >= 0)),
    ]
    source = "涉河建设项目洪水影响评价净空复核通用关系；按设计洪水位、壅水高度及安全超高控制。"
    return {
        "program_id": "3-17",
        "title": "净空校核",
        "source": source,
        "metrics": outputs,
        "tables": [_table("净空校核表", outputs)],
        "clearance_margin_m": _safe_round(margin, 3),
        "pass": margin >= 0,
        "calculation_book_text": _book("净空校核计算书", source, [
            _metric("设计洪水位", design_level, "m"),
            _metric("壅水高度", backwater, "m"),
            _metric("安全超高", freeboard, "m"),
            _metric("梁底/结构底高程", bottom, "m"),
        ], outputs, ["控制水位 = 设计洪水位 + 壅水高度", "所需梁底高程 = 控制水位 + 安全超高", "净空富余 = 实际梁底高程 - 所需梁底高程"]),
    }


# @hook UI3-1 3-12 20260804
# Checks foundation embedment safety after general/local scour.
def calculate_str_05(params: dict[str, Any]) -> dict[str, Any]:
    bed = _num(params, "bed_elevation_m")
    general_depth = max(0.0, _num(params, "general_scour_depth_m", 0.0))
    local_depth = max(0.0, _num(params, "local_scour_depth_m", 0.0))
    foundation = _num(params, "foundation_bottom_elevation_m")
    required_embedment = max(0.0, _num(params, "required_embedment_m", 0.0))
    scour_line = bed - general_depth - local_depth
    actual_embedment = scour_line - foundation
    margin = actual_embedment - required_embedment
    outputs = [
        _metric("现状河床高程", _safe_round(bed, 3), "m"),
        _metric("一般冲刷深度", _safe_round(general_depth, 3), "m"),
        _metric("局部冲刷深度", _safe_round(local_depth, 3), "m"),
        _metric("控制冲刷线高程", _safe_round(scour_line, 3), "m"),
        _metric("基础底高程", _safe_round(foundation, 3), "m"),
        _metric("实际埋深", _safe_round(actual_embedment, 3), "m"),
        _metric("要求埋深", _safe_round(required_embedment, 3), "m"),
        _metric("富余埋深", _safe_round(margin, 3), "m", _status(margin >= 0)),
    ]
    source = "涉河建设项目洪水影响评价基础埋深安全复核；以冲刷线与基础底高程关系判别。"
    return {
        "program_id": "3-12",
        "title": "基础埋深安全",
        "source": source,
        "metrics": outputs,
        "tables": [_table("基础埋深安全校核表", outputs)],
        "embedment_margin_m": _safe_round(margin, 3),
        "pass": margin >= 0,
        "calculation_book_text": _book("基础埋深安全校核计算书", source, [
            _metric("现状河床高程", bed, "m"),
            _metric("一般冲刷深度", general_depth, "m"),
            _metric("局部冲刷深度", local_depth, "m"),
            _metric("基础底高程", foundation, "m"),
            _metric("要求埋深", required_embedment, "m"),
        ], outputs, ["冲刷线高程 = 河床高程 - 一般冲刷深度 - 局部冲刷深度", "实际埋深 = 冲刷线高程 - 基础底高程", "富余埋深 = 实际埋深 - 要求埋深"]),
    }


# @hook UI3-1 3-14 20260804
# Generates the upstream backwater influence curve and key section water-level table.
def calculate_str_06(params: dict[str, Any]) -> dict[str, Any]:
    max_height = max(0.0, _num(params, "max_backwater_height_m"))
    length = max(0.0, _num(params, "backwater_length_m"))
    design_level = _num(params, "design_water_level_m", 0.0)
    slope = max(0.0, _num(params, "bed_slope", 0.0))
    steps = int(max(2, min(80, _num(params, "curve_steps", 12))))
    points = _curve_points(max_height, length, design_level, slope, steps)
    outputs = [
        _metric("最大壅水高度", _safe_round(max_height, 3), "m"),
        _metric("上游影响长度", _safe_round(length, 2), "m"),
        _metric("控制断面水位", _safe_round(design_level + max_height, 3), "m"),
        _metric("计算步数", steps, "段"),
    ]
    source = "涉河建设项目壅水影响范围复核；曲线采用二次衰减概化，用于成果展示与断面影响初判。"
    return {
        "program_id": "3-14",
        "title": "壅水曲线与上游回水影响",
        "source": source,
        "metrics": outputs,
        "tables": [
            _table("壅水影响成果表", outputs),
            _table("上游回水曲线点表", [_metric(f"{p['distance_m']} m", p["water_level_m"], "m", f"壅水 {p['backwater_m']} m") for p in points]),
        ],
        "curve_points": points,
        "calculation_book_text": _book("壅水曲线与上游回水影响计算书", source, [
            _metric("最大壅水高度", max_height, "m"),
            _metric("影响长度", length, "m"),
            _metric("设计洪水位", design_level, "m"),
            _metric("河底坡降", slope, ""),
        ], outputs, ["h(x)=h0(1-x/L)^2", "Z(x)=Z0+h(x)-i·x"]),
    }
