from __future__ import annotations

import json
from typing import Any

from . import core
from .core import HydrologyError, _require_positive, _safe_round


DEFAULT_FREQUENCIES = ("100", "50", "20", "10")


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:
        return float(value)
    except (TypeError, ValueError) as exc:
        raise HydrologyError(f"{key} 必须是数字") from exc


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


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 _engineering_table(title: str, columns: list[str], rows: list[dict[str, Any]]) -> dict[str, Any]:
    return {"title": title, "columns": columns, "rows": rows}


def _fmt(value: Any, digits: int = 2) -> str:
    if isinstance(value, str):
        return value
    try:
        return f"{float(value):.{digits}f}"
    except (TypeError, ValueError):
        return "" if value in (None, "") else str(value)


def _series(
    title: str,
    points: list[dict[str, Any]],
    x_key: str,
    y_key: str,
    unit: str = "",
) -> dict[str, Any]:
    return {"title": title, "points": points, "xKey": x_key, "yKey": y_key, "unit": unit}


def _wsl_rating_curve_for_section(
    section: core.HydraulicSectionInput,
    slope: float,
    design_level: float,
) -> tuple[list[dict[str, str]], list[dict[str, float]]]:
    points = tuple(sorted(section.points))
    min_z = min(z for _x, z in points)
    max_z = max(z for _x, z in points)
    top_level = max(max_z, design_level) + max(0.5, (max_z - min_z) * 0.15)
    bottom_level = min_z + max(0.05, (top_level - min_z) / 24)
    step = max(0.10, (top_level - bottom_level) / 14)
    levels = [bottom_level + step * index for index in range(15)]
    if all(abs(level - design_level) > step * 0.35 for level in levels):
        levels.append(design_level)
    levels = sorted({round(level, 4) for level in levels if level > min_z})
    slope_value = slope if slope and slope > 0 else 1e-6
    rows: list[dict[str, str]] = []
    curve: list[dict[str, float]] = []
    for level in levels:
        q, area, wetted, radius, top_width, max_depth = core.manning_flow_for_level(
            points,
            level,
            section.roughness_n,
            slope_value,
        )
        velocity = q / area if area > 0 else 0.0
        rows.append(
            {
                "水位(m)": _fmt(level),
                "水深(m)": _fmt(max_depth),
                "过水面积(m²)": _fmt(area),
                "湿周(m)": _fmt(wetted),
                "水力半径(m)": _fmt(radius),
                "水面宽(m)": _fmt(top_width),
                "平均流速(m/s)": _fmt(velocity),
                "流量(m³/s)": _fmt(q),
            }
        )
        curve.append(
            {
                "water_level_m": _safe_round(level, 3),
                "depth_m": _safe_round(max_depth, 3),
                "discharge_m3s": _safe_round(q, 3),
            }
        )
    return rows, curve


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 _frequencies(params: dict[str, Any]) -> tuple[str, ...]:
    raw = _text(params, "frequencies", ",".join(DEFAULT_FREQUENCIES))
    items = [item.strip() for item in raw.replace("，", ",").split(",") if item.strip()]
    if not items:
        raise HydrologyError("至少需要一个设计频率")
    invalid = [item for item in items if item not in core.DESIGN_FREQUENCY_PROBABILITIES]
    if invalid:
        raise HydrologyError(f"暂不支持的设计频率：{', '.join(invalid)}")
    return tuple(items)


def _rainfall_design(params: dict[str, Any]) -> core.InstantRainfallDesignInput:
    return core.InstantRainfallDesignInput(
        h1_mean_mm=_require_positive("1h点雨量均值", _num(params, "h1_mean_mm")),
        h1_cv=_require_positive("1h Cv", _num(params, "h1_cv", 0.45)),
        h6_mean_mm=_require_positive("6h点雨量均值", _num(params, "h6_mean_mm")),
        h6_cv=_require_positive("6h Cv", _num(params, "h6_cv", 0.50)),
        h24_mean_mm=_require_positive("24h点雨量均值", _num(params, "h24_mean_mm")),
        h24_cv=_require_positive("24h Cv", _num(params, "h24_cv", 0.55)),
        alpha1=_require_positive("1h点面折减系数", _num(params, "alpha1", 1.0)),
        alpha3=_require_positive("3h点面折减系数", _num(params, "alpha3", 1.0)),
        alpha6=_require_positive("6h点面折减系数", _num(params, "alpha6", 1.0)),
        alpha24=_require_positive("24h点面折减系数", _num(params, "alpha24", 1.0)),
        gamma1=_require_positive("1h形状修正系数", _num(params, "gamma1", 1.0)),
        gamma3=_require_positive("3h形状修正系数", _num(params, "gamma3", 1.0)),
        gamma6=_require_positive("6h形状修正系数", _num(params, "gamma6", 1.0)),
        gamma24=_require_positive("24h形状修正系数", _num(params, "gamma24", 1.0)),
        cs_ratio=_require_positive("Cs/Cv", _num(params, "cs_ratio", 3.5)),
    )


def _runoff_settings(params: dict[str, Any], freqs: tuple[str, ...]) -> list[core.FrequencyRunoffSetting]:
    infiltration_table = _text(params, "infiltration_table_id", "huangtu")
    unit_param_region = _text(params, "unit_param_region", "huangtu")
    runoff_duration_raw = _text(params, "runoff_duration_h", "")
    infiltration_raw = _text(params, "infiltration_rate_mmh", "")
    runoff_duration = int(float(runoff_duration_raw)) if runoff_duration_raw else None
    infiltration_rate = float(infiltration_raw) if infiltration_raw else None
    return [
        core.FrequencyRunoffSetting(
            frequency=freq,
            infiltration_rate_mmh=infiltration_rate,
            runoff_duration_h=runoff_duration,
            unit_param_region=unit_param_region,
            infiltration_table_id=infiltration_table,
        )
        for freq in freqs
    ]


def _distribution(params: dict[str, Any], area_km2: float) -> core.InstantRainfallDistribution:
    zone_id = _text(params, "rainfall_zone_id", "huanghe")
    return core.select_instant_rainfall_distribution(zone_id, area_km2)


def _rainfall_table_rows(
    frequencies: tuple[str, ...],
    kp_by_duration: dict[str, dict[str, float]],
    point_by_duration: dict[str, dict[str, float]],
    areal_by_duration: dict[str, dict[str, float]],
) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for duration in ("1h", "3h", "6h", "24h"):
        for freq in frequencies:
            rows.append(
                _metric(
                    f"{duration} · {freq}年",
                    _safe_round(areal_by_duration[duration][freq], 3),
                    "mm",
                    f"Kp={kp_by_duration[duration][freq]:.3f}；点雨量={point_by_duration[duration][freq]:.3f} mm",
                )
            )
    return rows


def _net_rain_rows(result: core.InstantUnitLineBatchResult, frequencies: tuple[str, ...]) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for row in result.net_rain_table:
        for freq in frequencies:
            rows.append(
                _metric(
                    f"{row.hour}h · {freq}年",
                    row.net_rain_mm[freq],
                    "mm",
                    f"面雨量={row.rainfall_mm[freq]} mm；入渗量={row.infiltration_mm[freq]} mm",
                )
            )
    return rows


def _instant_series(result: core.InstantUnitLineBatchResult, frequencies: tuple[str, ...]) -> list[dict[str, Any]]:
    series: list[dict[str, Any]] = []
    for freq in frequencies:
        flood = result.flood_results[freq]
        series.append(
            _series(
                f"{freq}年面雨量过程",
                [{"time_h": row.hour, "rainfall_mm": row.rainfall_mm[freq]} for row in result.net_rain_table],
                "time_h",
                "rainfall_mm",
                "mm",
            )
        )
        series.append(
            _series(
                f"{freq}年入渗量过程",
                [{"time_h": row.hour, "infiltration_mm": row.infiltration_mm[freq]} for row in result.net_rain_table],
                "time_h",
                "infiltration_mm",
                "mm",
            )
        )
        series.append(
            _series(
                f"{freq}年洪水过程线",
                [{"time_h": item.time_h, "discharge_m3s": item.discharge_m3s} for item in flood.hydrograph],
                "time_h",
                "discharge_m3s",
                "m³/s",
            )
        )
        series.append(
            _series(
                f"{freq}年瞬时单位线 S(t)",
                [{"time_h": item.time_h, "unit_s": item.unit_depth_fraction} for item in flood.unit_line],
                "time_h",
                "unit_s",
                "S(t)",
            )
        )
        series.append(
            _series(
                f"{freq}年净雨过程",
                [{"time_h": row.hour, "net_rain_mm": row.net_rain_mm[freq]} for row in result.net_rain_table],
                "time_h",
                "net_rain_mm",
                "mm",
            )
        )
    return series


def calculate_fld_01(params: dict[str, Any]) -> dict[str, Any]:
    # @hook UI3-2 1-01 20260804
    # Flow-basin design rainfall adapter reusing core.instant_duration_design_rainfall.
    freqs = _frequencies(params)
    area = _require_positive("流域面积F", _num(params, "area_km2"))
    rainfall_design = _rainfall_design(params)
    kp_by_duration, point_by_duration, areal_by_duration = core.instant_duration_design_rainfall(rainfall_design, freqs)
    source = "复用 core.instant_duration_design_rainfall；3h按H3=H1^0.387×H6^0.613，面雨量=点雨量×α×γ。"
    rows = _rainfall_table_rows(freqs, kp_by_duration, point_by_duration, areal_by_duration)
    input_rows = [_metric("流域面积F", area, "km²"), _metric("Cs/Cv", rainfall_design.cs_ratio)]
    for duration in (1, 6, 24):
        input_rows.extend([
            _metric(f"{duration}h点雨量均值", getattr(rainfall_design, f"h{duration}_mean_mm"), "mm", "输入统计资料"),
            _metric(f"{duration}h变差系数Cv", getattr(rainfall_design, f"h{duration}_cv"), "", "输入统计资料"),
        ])
    engineering_rows = []
    for duration in (1, 3, 6, 24):
        alpha = getattr(rainfall_design, f"alpha{duration}")
        gamma = getattr(rainfall_design, f"gamma{duration}")
        input_rows.extend([_metric(f"{duration}h点面折减系数α", alpha), _metric(f"{duration}h形状修正系数γ", gamma)])
        for freq in freqs:
            point = point_by_duration[f"{duration}h"][freq]
            engineering_rows.append({
                "重现期（年）": freq, "设计频率（%）": core.DESIGN_FREQUENCY_PROBABILITIES[freq],
                "控制历时（h）": duration,
                "点雨量均值（mm）": "" if duration == 3 else getattr(rainfall_design, f"h{duration}_mean_mm"),
                "变差系数Cv": "" if duration == 3 else getattr(rainfall_design, f"h{duration}_cv"),
                "Cs/Cv": rainfall_design.cs_ratio,
                "Kp": "" if duration == 3 else kp_by_duration[f"{duration}h"][freq],
                "设计点雨量（mm）": point, "点面折减系数α": alpha, "形状修正系数γ": gamma,
                "设计面雨量（mm）": areal_by_duration[f"{duration}h"][freq],
                "计算依据": "H3=H1^0.387×H6^0.613；无独立3h统计Kp" if duration == 3 else "Pearson III：点雨量=均值×Kp",
            })
    columns = list(engineering_rows[0])
    book = _book(
        "流域参数与设计暴雨计算书", source, input_rows, rows,
        ["Pp = P均值 × Kp", "H3 = H1^0.387 × H6^0.613", "面雨量 = 点雨量 × 点面折减系数α × 流域形状修正系数γ"],
    )
    book += "\n\n控制历时设计暴雨固定成果表\n| " + " | ".join(columns) + " |\n| " + " | ".join("---" for _ in columns) + " |\n"
    book += "\n".join("| " + " | ".join(str(row[column]) for column in columns) + " |" for row in engineering_rows)
    peak_24h = max(areal_by_duration["24h"].values())
    return {
        "program_id": "1-01",
        "title": "流域参数与设计暴雨",
        "source": source,
        "metrics": [
            _metric("流域面积F", area, "km²"),
            _metric("最大24h面雨量", peak_24h, "mm", "所选频率中的最大值"),
        ],
        "tables": [_engineering_table("控制时段点雨量、Kp与面雨量", columns, engineering_rows), _table("统计参数及折减系数", input_rows)],
        "calculation_book_text": book,
    }


def calculate_fld_04(params: dict[str, Any]) -> dict[str, Any]:
    # @hook UI3-2 1-04 20260804
    # Instant unit line adapter reusing core.calculate_instant_unit_line_atlas_batch.
    freqs = _frequencies(params)
    area = _require_positive("流域面积F", _num(params, "area_km2"))
    rainfall_design = _rainfall_design(params)
    distribution = _distribution(params, area)
    result = core.calculate_instant_unit_line_atlas_batch(
        area_km2=area,
        river_slope_permille=_require_positive("主河槽比降J", _num(params, "river_slope_permille")),
        rainfall_distribution=distribution,
        rainfall_design=rainfall_design,
        runoff_settings=_runoff_settings(params, freqs),
        delta_t_h=_require_positive("计算步长Δt", _num(params, "delta_t_h", 1.0)),
    )
    peak_rows = [
        _metric(freq, flood.peak_flow_m3s, "m³/s", f"峰现时间={flood.peak_time_h} h；洪量={flood.flood_volume_10k_m3} 万m³")
        for freq, flood in result.flood_results.items()
        if freq in freqs
    ]
    unit_rows: list[dict[str, Any]] = []
    for freq in freqs:
        for item in result.flood_results[freq].unit_line:
            unit_rows.append(_metric(f"{freq}年 t={item.time_h}h", item.unit_depth_fraction, "", f"t/k={item.t_over_k}；S(t)={item.s_t}"))
    return {
        "program_id": "1-04",
        "title": "瞬时单位线法",
        "source": "复用 core.calculate_instant_unit_line_atlas_batch；按图集雨型、入渗表和Nash瞬时单位线生成洪水过程。",
        "metrics": peak_rows,
        "tables": [
            _table("洪峰与洪量成果", peak_rows),
            _table("面雨量、入渗量与净雨过程", _net_rain_rows(result, freqs)),
            _table("单位线主要时段", unit_rows),
        ],
        "series": _instant_series(result, freqs),
        "peak_flow": max(flood.peak_flow_m3s for flood in result.flood_results.values()),
        "rain_type": f"{distribution.zone_name} · {distribution.main_peak_hours}h主峰",
        "calculation_book_text": core.instant_unit_line_audit_result_to_text(result, freqs),
    }


def calculate_fld_05(params: dict[str, Any]) -> dict[str, Any]:
    # @hook UI3-2 1-05 20260804
    # Rational formula adapter reusing core.calculate_rational_formula_atlas_batch.
    freqs = _frequencies(params)
    area = _require_positive("流域面积F", _num(params, "area_km2"))
    rainfall_design = _rainfall_design(params)
    distribution = _distribution(params, area)
    result = core.calculate_rational_formula_atlas_batch(
        area_km2=area,
        river_length_km=_require_positive("主河道长度L", _num(params, "river_length_km")),
        river_slope_permille=_require_positive("主河槽比降J", _num(params, "river_slope_permille")),
        rainfall_distribution=distribution,
        rainfall_design=rainfall_design,
        runoff_settings=_runoff_settings(params, freqs),
        rational_region_id=_text(params, "rational_region_id", "huangtu"),
        delta_t_h=_require_positive("计算步长Δt", _num(params, "delta_t_h", 1.0)),
    )
    peak_rows = [
        _metric(freq, flood.peak_flow_m3s, "m³/s", f"τ={flood.tau_h} h；峰现={flood.peak_time_h} h；条件={flood.runoff_condition}")
        for freq, flood in result.frequency_results.items()
        if freq in freqs
    ]
    curve_rows: list[dict[str, Any]] = []
    for freq in freqs:
        for t_h, h_t, q_t, tau_t in result.frequency_results[freq].confluence_curve_points:
            curve_rows.append(_metric(f"{freq}年 t={t_h}h", q_t, "m³/s", f"Ht={h_t} mm；τ(t)={tau_t} h"))
    series: list[dict[str, Any]] = []
    for freq in freqs:
        flood = result.frequency_results[freq]
        series.append(
            _series(
                f"{freq}年面雨量过程",
                [{"time_h": row.hour, "rainfall_mm": row.rainfall_mm[freq]} for row in result.shared.net_rain_table],
                "time_h",
                "rainfall_mm",
                "mm",
            )
        )
        series.append(
            _series(
                f"{freq}年入渗量过程",
                [{"time_h": row.hour, "infiltration_mm": row.infiltration_mm[freq]} for row in result.shared.net_rain_table],
                "time_h",
                "infiltration_mm",
                "mm",
            )
        )
        series.append(
            _series(
                f"{freq}年净雨过程",
                [{"time_h": row.hour, "net_rain_mm": row.net_rain_mm[freq]} for row in result.shared.net_rain_table],
                "time_h",
                "net_rain_mm",
                "mm",
            )
        )
        series.append(
            _series(
                f"{freq}年洪水过程线",
                [{"time_h": item.time_h, "discharge_m3s": item.discharge_m3s} for item in flood.hydrograph],
                "time_h",
                "discharge_m3s",
                "m³/s",
            )
        )
        series.append(
            _series(
                f"{freq}年汇流曲线",
                [
                    {"time_h": t_h, "discharge_m3s": q_t, "tau_h": tau_t, "rain_depth_mm": h_t}
                    for t_h, h_t, q_t, tau_t in flood.confluence_curve_points
                ],
                "time_h",
                "discharge_m3s",
                "m³/s",
            )
        )
    return {
        "program_id": "1-05",
        "title": "推理公式法",
        "source": "复用 core.calculate_rational_formula_atlas_batch；按图集推理公式求τ与洪峰，并生成五点概化洪水过程。",
        "metrics": peak_rows,
        "tables": [
            _table("洪峰与汇流参数", peak_rows),
            _table("汇流曲线试算点", curve_rows),
            _table("面雨量、入渗量与净雨过程", _net_rain_rows(result.shared, freqs)),
        ],
        "series": series,
        "peak_flow": max(flood.peak_flow_m3s for flood in result.frequency_results.values()),
        "rain_type": f"{distribution.zone_name} · {distribution.main_peak_hours}h主峰",
        "calculation_book_text": core.rational_formula_audit_result_to_text(result, freqs),
    }


def calculate_wsl_01(params: dict[str, Any]) -> dict[str, Any]:
    # @hook UI3-2 4-01 20260804
    # HEC standard-step water-surface profile adapter reusing core.calculate_hec_profile.
    sections = _parse_sections(_text(params, "sections_json", ""))
    lengths = _parse_float_list(_text(params, "reach_lengths_m", "300"))
    upstream_raw = _text(params, "upstream_water_level_m", "")
    regime = core._normalize_flow_regime(_text(params, "flow_regime", "subcritical"))
    if regime == 'supercritical' and not upstream_raw:
        raise HydrologyError('超临界流必须填写上游控制水位。')
    downstream = _num(params, 'downstream_water_level_m') if regime == 'subcritical' else 0.0
    result = core.calculate_hec_profile(
        core.HecReachInput(
            flow_m3s=_require_positive("设计流量Q", _num(params, "flow_m3s")),
            sections_downstream_to_upstream=tuple(sections),
            reach_lengths_m=tuple(lengths),
            downstream_water_level_m=downstream,
            upstream_water_level_m=float(upstream_raw) if upstream_raw else None,
            flow_regime=regime,
            contraction_coeff=_num(params, "contraction_coeff", 0.1),
            expansion_coeff=_num(params, "expansion_coeff", 0.3),
            friction_method=_text(params, "friction_method", "average_conveyance"),
        )
    )
    cumulative_distances = [0.0]
    for length in lengths:
        cumulative_distances.append(cumulative_distances[-1] + length)
    while len(cumulative_distances) < len(result.states_downstream_to_upstream):
        cumulative_distances.append(cumulative_distances[-1])
    state_rows = []
    for idx, state in enumerate(result.states_downstream_to_upstream):
        critical = core.critical_water_level_for_section(sections[idx].points, result.flow_m3s)
        normal_slope = params.get('normal_depth_slope')
        normal = core.normal_water_level_for_section(sections[idx], result.flow_m3s, float(normal_slope)) if normal_slope not in (None, '') else None
        state_rows.append(
            {
                "断面": state.name,
                "距下游(m)": _fmt(cumulative_distances[idx]),
                "河床最低高程(m)": _fmt(state.thalweg_elevation_m),
                "计算水位(m)": _fmt(state.water_level_m),
                "水深(m)": _fmt(state.max_depth_m),
                "过水面积(m²)": _fmt(state.area_m2),
                "湿周(m)": _fmt(state.wetted_perimeter_m),
                "水力半径(m)": _fmt(state.hydraulic_radius_m),
                "水面宽(m)": _fmt(state.top_width_m),
                "平均流速(m/s)": _fmt(state.velocity_ms),
                "速度水头(m)": _fmt(state.velocity_head_m),
                "Fr": _fmt(state.froude_number, 3),
                "能量线(m)": _fmt(state.energy_grade_m),
                "输水模数K": _fmt(state.flow_modulus_k),
                "摩阻坡降": _fmt(state.friction_slope, 5),
                "临界水位(m)": _fmt(critical[0], 3),
                "临界水深(m)": _fmt(critical[1], 3),
                "正常水位(m)": _fmt(normal[0], 3) if normal else '—',
                "正常水深(m)": _fmt(normal[1], 3) if normal else '—',
            }
        )
    step_rows = []
    for step in result.steps:
        step_rows.append(
            {
                "计算段": f"{step.downstream_section}→{step.upstream_section}",
                "段长(m)": _fmt(step.reach_length_m),
                "下游水位(m)": _fmt(step.downstream_water_level_m),
                "上游水位(m)": _fmt(step.upstream_water_level_m),
                "摩阻损失(m)": _fmt(step.friction_loss_m),
                "局部损失(m)": _fmt(step.local_loss_m),
                "总损失(m)": _fmt(step.total_loss_m),
                "能量残差(m)": _fmt(step.energy_residual_m, 5),
            }
        )
    metric_rows = [
        _metric(
            state.name,
            state.water_level_m,
            "m",
            f"V={_fmt(state.velocity_ms)} m/s；Fr={_fmt(state.froude_number, 3)}；A={_fmt(state.area_m2)} m²",
        )
        for state in result.states_downstream_to_upstream
    ]
    profile_points = [
        {
            "distance_m": cumulative_distances[idx],
            "water_level_m": state.water_level_m,
            "energy_grade_m": state.energy_grade_m,
            "bed_elevation_m": state.thalweg_elevation_m,
        }
        for idx, state in enumerate(result.states_downstream_to_upstream)
    ]
    section_profiles = [
        {
            "name": section.name,
            "points": [{"x": x, "z": z} for x, z in section.points],
            "water_level": state.water_level_m,
            "thalweg_x": state.thalweg_x_m,
            "thalweg_z": state.thalweg_elevation_m,
            "design_flow_m3s": _num(params, "flow_m3s"),
            "velocity_ms": state.velocity_ms,
            "area_m2": state.area_m2,
        }
        for section, state in zip(sections, result.states_downstream_to_upstream)
    ]
    representative_index = len(result.states_downstream_to_upstream) - 1
    representative_state = result.states_downstream_to_upstream[representative_index]
    representative_section = sections[min(representative_index, len(sections) - 1)]
    rating_rows, rating_curve = _wsl_rating_curve_for_section(
        representative_section,
        representative_state.friction_slope,
        representative_state.water_level_m,
    )
    source = "按多断面能量方程标准步法沿程推求水面线；不接入外部 HEC-RAS 时使用软件内置能量方程迭代。"
    return {
        "program_id": "4-01",
        "title": "水面线推求",
        "source": source,
        "metrics": [
            _metric("流态", result.flow_regime, ""),
            _metric("计算方向", result.calculation_direction, ""),
            _metric("断面数", len(result.states_downstream_to_upstream), "个"),
        ],
        "diag": [result.boundary_note, '正常水位仅在提供河床比降时计算；未提供时留空，不用计算摩阻坡降冒充河床坡降。'],
        "tables": [
            _engineering_table(
                "水位计算成果表",
                ["断面", "距下游(m)", "河床最低高程(m)", "计算水位(m)", "水深(m)", "过水面积(m²)", "湿周(m)", "水力半径(m)", "水面宽(m)", "平均流速(m/s)", "速度水头(m)", "Fr", "能量线(m)", "输水模数K", "摩阻坡降", "临界水位(m)", "临界水深(m)", "正常水位(m)", "正常水深(m)"],
                state_rows,
            ),
            _engineering_table(
                "能量方程水力要素复核表",
                ["断面", "距下游(m)", "水深(m)", "过水面积(m²)", "湿周(m)", "水力半径(m)", "平均流速(m/s)", "速度水头(m)", "能量线(m)", "Fr", "摩阻坡降"],
                state_rows,
            ),
            _engineering_table(
                "多断面能量方程标准步法逐段计算表",
                ["计算段", "段长(m)", "下游水位(m)", "上游水位(m)", "摩阻损失(m)", "局部损失(m)", "总损失(m)", "能量残差(m)"],
                step_rows,
            ),
            _engineering_table(
                f"{representative_state.name}水位流量关系计算表",
                ["水位(m)", "水深(m)", "过水面积(m²)", "湿周(m)", "水力半径(m)", "水面宽(m)", "平均流速(m/s)", "流量(m³/s)"],
                rating_rows,
            ),
        ],
        "series": [
            _series("沿程设计洪水位", profile_points, "distance_m", "water_level_m", "m"),
            _series("沿程能量线", profile_points, "distance_m", "energy_grade_m", "m"),
            _series("沿程河床线", profile_points, "distance_m", "bed_elevation_m", "m"),
        ],
        "profile_curve": profile_points,
        "rating_curve": rating_curve,
        "rating_marker": {
            "water_level_m": representative_state.water_level_m,
            "discharge_m3s": result.flow_m3s,
            "label": f"{representative_state.name}设计流量对应水位",
        },
        "water_level": result.states_downstream_to_upstream[-1].water_level_m,
        "section_profiles": section_profiles,
        "section_profile": section_profiles[-1] if section_profiles else None,
        "calculation_book_text": _book(
            "水面线推求计算书",
            source,
            [_metric("设计流量", _num(params, "flow_m3s"), "m³/s"),
             _metric("下游控制水位" if regime == 'subcritical' else "上游控制水位", downstream if regime == 'subcritical' else float(upstream_raw), "m")],
            metric_rows,
            [
                "E上游 = E下游 + hf + hj",
                "E = Z + αV²/(2g)，hf按相邻断面输水能力平均法求取，hj按收缩/扩散局部损失系数计算。",
                "同步输出沿程水位线、能量线、河床线、代表断面水位流量关系曲线和各断面水位标注图，用于水文计算书复核。",
            ],
        ),
    }


def _parse_sections(raw: str) -> list[core.HydraulicSectionInput]:
    if not raw.strip():
        raw = json.dumps(
            [
                {"name": "下游断面", "roughness_n": 0.035, "points": [[0, 103], [8, 101], [16, 100.2], [25, 100.4], [34, 102], [42, 103.5]]},
                {"name": "上游断面", "roughness_n": 0.035, "points": [[0, 103.4], [8, 101.4], [16, 100.6], [25, 100.8], [34, 102.4], [42, 103.9]]},
            ],
            ensure_ascii=False,
        )
    try:
        rows = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise HydrologyError("断面点列必须是 JSON 数组") from exc
    sections: list[core.HydraulicSectionInput] = []
    for index, row in enumerate(rows):
        points = tuple((float(x), float(z)) for x, z in row["points"])
        if len(points) < 3:
            raise HydrologyError("每个断面至少需要3个点")
        sections.append(
            core.HydraulicSectionInput(
                name=str(row.get("name") or f"断面{index + 1}"),
                points=points,
                roughness_n=_require_positive("糙率n", float(row.get("roughness_n", 0.035))),
                alpha=float(row.get("alpha", 1.0)),
            )
        )
    if len(sections) < 2:
        raise HydrologyError("水面线至少需要2个断面")
    return sections


def _parse_float_list(raw: str) -> list[float]:
    values = [float(item.strip()) for item in raw.replace("，", ",").split(",") if item.strip()]
    if not values:
        raise HydrologyError("河段长度不能为空")
    return values
